pax_global_header00006660000000000000000000000064147545162050014523gustar00rootroot0000000000000052 comment=819287e04a52fdd2c280e6a13585101a54e5048a cyclopts-3.9.0/000077500000000000000000000000001475451620500133745ustar00rootroot00000000000000cyclopts-3.9.0/.codecov.yml000066400000000000000000000006011475451620500156140ustar00rootroot00000000000000coverage: status: project: default: # Commits pushed to main should not make the overall # project coverage decrease by more than 2% target: auto threshold: 2% patch: default: # Be tolerant on code coverage diff on PRs to limit # noisy red coverage status on github PRs. target: auto threshold: 20% cyclopts-3.9.0/.github/000077500000000000000000000000001475451620500147345ustar00rootroot00000000000000cyclopts-3.9.0/.github/FUNDING.yml000066400000000000000000000014561475451620500165570ustar00rootroot00000000000000# These are supported funding model platforms github: [BrianPugh]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] cyclopts-3.9.0/.github/contributing.md000066400000000000000000000054771475451620500200020ustar00rootroot00000000000000## Environment Setup 1. We use [Poetry](https://python-poetry.org/docs/#installation) for managing virtual environments and dependencies. Once Poetry is installed, run `poetry install` in this repo to get started. 2. For managing linters, static-analysis, and other tools, we use [pre-commit](https://pre-commit.com/#installation). Once Pre-commit is installed, run `pre-commit install` in this repo to install the hooks. Using pre-commit ensures PRs match the linting requirements of the codebase. ## Documentation Whenever possible, please add docstrings to your code! We use [numpy-style napoleon docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/#google-vs-numpy). To confirm docstrings are valid, build the docs by running `poetry run make html` in the `docs/` folder. I typically write docstrings first, it will act as a guide to limit scope and encourage unit-testable code. Good docstrings include information like: 1. If not immediately obvious, what is the intended use-case? When should this function be used? 2. What happens during errors/edge-cases. 3. When dealing with physical values, include units. ## Unit Tests We use the [pytest](https://docs.pytest.org/) framework for unit testing. Ideally, all new code is partners with new unit tests to exercise that code. If fixing a bug, consider writing the test first to confirm the existence of the bug, and to confirm that the new code fixes it. Unit tests should only test a single concise body of code. If this is hard to do, there are two solutions that can help: 1. Restructure the code. Keep inputs/outputs to be simple variables. Avoid complicated interactions with state. 2. Use [pytest-mock](https://pytest-mock.readthedocs.io/en/latest/) to mock out external interactions. ## Coding Style In an attempt to keep consistency and maintainability in the code-base, here are some high-level guidelines for code that might not be enforced by linters. * Use f-strings. * Keep/cast path variables as `pathlib.Path` objects. Do not use `os.path`. For public-facing functions, cast path arguments immediately to `Path`. * Use magic-methods when appropriate. It might be better to implement ``MyClass.__call__()`` instead of ``MyClass.run()``. * Do not return sentinel values for error-states like `-1` or `None`. Instead, raise an exception. * Avoid deeply nested code. Techniques like returning early and breaking up a complicated function into multiple functions results in easier to read and test code. * Consider if you are double-name-spacing and how modules are meant to be imported. E.g. it might be better to name a function `read` instead of `image_read` in the module `my_package/image.py`. Consider the module name-space and whether or not it's flattened in `__init__.py`. * Only use multiple-inheritance if using a mixin. Mixin classes should end in `"Mixin"`. cyclopts-3.9.0/.github/dependabot.yml000066400000000000000000000007661475451620500175750ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" cyclopts-3.9.0/.github/workflows/000077500000000000000000000000001475451620500167715ustar00rootroot00000000000000cyclopts-3.9.0/.github/workflows/deploy.yaml000066400000000000000000000040031475451620500211460ustar00rootroot00000000000000name: Build package and push to PyPi on: workflow_dispatch: push: tags: - "v*.*.*" jobs: build: runs-on: ubuntu-latest env: PYTHON: 3.12 POETRY_HOME: "~/poetry" steps: - name: Check out repository uses: actions/checkout@v4 with: fetch-depth: 0 # Includes getting tags - name: Set up python ${{ env.PYTHON }} id: setup-python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON }} - name: Cache Poetry Install uses: actions/cache@v4 id: cached-poetry with: path: ${{ env.POETRY_HOME }} key: poetry-cache-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.github/workflows/deploy.yaml') }} - name: Install poetry uses: snok/install-poetry@v1 if: steps.cached-poetry.outputs.cache-hit != 'true' - name: Add Poetry to PATH # Needs to be separate from install-poetry because cache. run: | echo "$POETRY_HOME/bin" >> $GITHUB_PATH - name: Configure Poetry # Needs to be separate from install-poetry because cache. run: | poetry self add poetry-dynamic-versioning[plugin] poetry config virtualenvs.create true poetry config virtualenvs.in-project true - name: Cache venv uses: actions/cache@v4 id: cached-venv with: path: .venv/ key: venv-cache-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.github/workflows/deploy.yaml') }} - name: Install project run: poetry install --no-interaction - name: Build package run: poetry build - name: Publish package if: github.event_name != 'workflow_dispatch' run: | poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} poetry publish - uses: actions/upload-artifact@v4 if: always() with: name: dist path: dist/ cyclopts-3.9.0/.github/workflows/tests.yaml000066400000000000000000000100101475451620500210070ustar00rootroot00000000000000# Regular tests # # Use this to ensure your tests are passing on every push and PR (skipped on # pushes which only affect documentation). # # You should make sure you run jobs on at least the *oldest* and the *newest* # versions of python that your codebase is intended to support. name: tests on: push: branches: - main pull_request: jobs: test: timeout-minutes: 45 defaults: run: shell: bash runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-13, windows-latest] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] env: OS: ${{ matrix.os }} PYTHON: ${{ matrix.python-version }} POETRY_HOME: "~/poetry" steps: - name: Set OS Environment Variables (Windows) if: runner.os == 'Windows' run: | echo 'ACTIVATE_PYTHON_VENV=.venv/scripts/activate' >> $GITHUB_ENV - name: Set OS Environment Variables (not Windows) if: runner.os != 'Windows' run: | echo 'ACTIVATE_PYTHON_VENV=.venv/bin/activate' >> $GITHUB_ENV - name: Check out repository uses: actions/checkout@v4 - name: Set up python ${{ matrix.python-version }} id: setup-python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Cache Poetry Install uses: actions/cache@v4 id: cached-poetry with: path: ${{ env.POETRY_HOME }} key: poetry-cache-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.github/workflows/tests.yaml') }} - name: Install poetry uses: snok/install-poetry@v1 if: steps.cached-poetry.outputs.cache-hit != 'true' - name: Add Poetry to PATH # Needs to be separate from install-poetry because cache. run: | echo "$POETRY_HOME/bin" >> $GITHUB_PATH - name: Configure Poetry # Needs to be separate from install-poetry because cache. run: | poetry config virtualenvs.create true poetry config virtualenvs.in-project true poetry config installer.parallel ${{ runner.os != 'Windows' }} # Currently there seems to be some race-condition in windows - name: Cache venv uses: actions/cache@v4 id: cached-venv with: path: .venv/ key: venv-cache-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.github/workflows/tests.yaml') }} - name: Install library run: poetry install --no-interaction - name: Cache pre-commit uses: actions/cache@v4 with: path: ~/.cache/pre-commit/ key: pre-commit-${{ runner.os }}-${{ env.pythonLocation }}-${{ hashFiles('.pre-commit-config.yaml') }} - name: Pre-commit run run: | source ${{ env.ACTIVATE_PYTHON_VENV }} pre-commit run --show-diff-on-failure --color=always --all-files - name: Check tests folder existence id: check_test_files uses: andstor/file-existence-action@v3 with: files: "tests" - name: Run tests if: steps.check_test_files.outputs.files_exists == 'true' run: | source ${{ env.ACTIVATE_PYTHON_VENV }} python -m pytest --cov=cyclopts --cov-report term --cov-report xml --junitxml=testresults.xml coverage report - name: Upload coverage to Codecov if: steps.check_test_files.outputs.files_exists == 'true' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests env_vars: OS,PYTHON name: Python ${{ matrix.python-version }} on ${{ runner.os }} #---------------------------------------------- # make sure docs build #---------------------------------------------- - name: Build HTML docs run: | source ${{ env.ACTIVATE_PYTHON_VENV }} sphinx-build -b html -W docs/source/ docs/build/html cyclopts-3.9.0/.gitignore000066400000000000000000000100271475451620500153640ustar00rootroot00000000000000##--------------------------------------------------- # Automated documentation .gitignore files ##--------------------------------------------------- # Automatically generated API documentation stubs from sphinx-apidoc docs/source/packages # Automatically converting README from markdown to rST docs/bin docs/source/readme.rst docs/source/assets ##--------------------------------------------------- # Continuous Integration .gitignore files ##--------------------------------------------------- # Ignore test result XML files testresults.xml coverage.xml ##--------------------------------------------------- # Python default .gitignore ##--------------------------------------------------- # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so *.pyd # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation /docs/_build/ /docs/build/ # PyBuilder target/ # Pycharm /.idea/dictionaries /.idea/modules.xml /.idea/shelf /.idea/usage.statistics.xml /.idea/workspace.xml # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don’t work, or not # install all needed dependencies. #Pipfile.lock # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ ##--------------------------------------------------- # Windows default .gitignore ##--------------------------------------------------- # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk ##--------------------------------------------------- # Linux default .gitignore ##--------------------------------------------------- # Editor backup files *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ##--------------------------------------------------- # Mac OSX default .gitignore ##--------------------------------------------------- # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # Cython cyclopts/_c_extension.c cyclopts/*.html # Poetry poetry.toml # line-profiler *.lprof # Misc dev /*.py /draw.toml cyclopts-3.9.0/.pre-commit-config.yaml000066400000000000000000000026541475451620500176640ustar00rootroot00000000000000exclude: ^(poetry.lock|.idea/) repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.7.1" hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-shebang-scripts-are-executable - id: check-merge-conflict - id: check-json - id: check-toml - id: check-xml - id: check-yaml - id: debug-statements exclude: '^tests/test_py3.*\.py$' - id: destroyed-symlinks - id: detect-private-key - id: end-of-file-fixer exclude: ^LICENSE|\.(html|csv|txt|svg|py)$ - id: pretty-format-json args: ["--autofix", "--no-ensure-ascii", "--no-sort-keys"] - id: requirements-txt-fixer - id: trailing-whitespace args: [--markdown-linebreak-ext=md] exclude: \.(html|svg)$ - repo: https://github.com/fredrikaverpil/creosote.git rev: v3.1.0 hooks: - id: creosote - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/crate-ci/typos rev: v1.26.8 hooks: - id: typos - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.387 hooks: - id: pyright cyclopts-3.9.0/.readthedocs.yaml000066400000000000000000000021101475451620500166150ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Derived from: https://docs.readthedocs.io/en/stable/build-customization.html#install-dependencies-with-poetry # Required version: 2 build: os: "ubuntu-22.04" tools: python: "3.10" jobs: post_create_environment: # Install poetry # https://python-poetry.org/docs/#installing-manually - pip install poetry - poetry self add poetry-dynamic-versioning post_install: # Install dependencies with 'docs' dependency group # https://python-poetry.org/docs/managing-dependencies/#dependency-groups # VIRTUAL_ENV needs to be set manually for now. # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --without=dev --with=docs # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py fail_on_warning: true # If using Sphinx, optionally build your docs in additional formats such as PDF formats: - pdf cyclopts-3.9.0/LICENSE000066400000000000000000000261351475451620500144100ustar00rootroot00000000000000 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 CURRENT_YEAR_HERE YOUR_NAME_HERE 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. cyclopts-3.9.0/README.md000066400000000000000000000232251475451620500146570ustar00rootroot00000000000000
![Python compat](https://img.shields.io/badge/>=python-3.9-blue.svg) [![PyPI](https://img.shields.io/pypi/v/cyclopts.svg)](https://pypi.org/project/cyclopts/) [![ReadTheDocs](https://readthedocs.org/projects/cyclopts/badge/?version=latest)](https://cyclopts.readthedocs.io) [![codecov](https://codecov.io/gh/BrianPugh/cyclopts/graph/badge.svg?token=HA393WIYUK)](https://codecov.io/gh/BrianPugh/cyclopts)
--- **Documentation:** https://cyclopts.readthedocs.io **Source Code:** https://github.com/BrianPugh/cyclopts --- Cyclopts is a modern, easy-to-use command-line interface (CLI) framework that aims to provide an intuitive & efficient developer experience. # Why Cyclopts? - **Intuitive API**: Quickly write CLI applications using a terse, intuitive syntax. - **Advanced Type Hinting**: Full support of all builtin types and even user-specified (yes, including [Pydantic](https://docs.pydantic.dev/latest/), [Dataclasses](https://docs.python.org/3/library/dataclasses.html), and [Attrs](https://www.attrs.org/en/stable/api.html)). - **Rich Help Generation**: Automatically generates beautiful help pages from **docstrings** and other contextual data. - **Extendable**: Easily customize converters, validators, token parsing, and application launching. # Installation Cyclopts requires Python >=3.9; to install Cyclopts, run: ```console pip install cyclopts ``` # Quick Start - Import `cyclopts.run()` and give it a function to run. ```python from cyclopts import run def foo(loops: int): for i in range(loops): print(f"Looping! {i}") run(foo) ``` Execute the script from the command line: ```console $ python start.py 3 Looping! 0 Looping! 1 Looping! 2 ``` When you need more control: - Create an application using `cyclopts.App`. - Register commands with the `command` decorator. - Register a default function with the `default` decorator. ```python from cyclopts import App app = App() @app.command def foo(loops: int): for i in range(loops): print(f"Looping! {i}") @app.default def default_action(): print("Hello world! This runs when no command is specified.") app() ``` Execute the script from the command line: ```console $ python demo.py Hello world! This runs when no command is specified. $ python demo.py foo 3 Looping! 0 Looping! 1 Looping! 2 ``` With just a few additional lines of code, we have a full-featured CLI app. See [the docs](https://cyclopts.readthedocs.io) for more advanced usage. # Compared to Typer Cyclopts is what you thought Typer was. Cyclopts's includes information from docstrings, support more complex types (even Unions and Literals!), and include proper validation support. See [the documentation for a complete Typer comparison](https://cyclopts.readthedocs.io/en/latest/vs_typer/README.html). Consider the following short 29-line Cyclopts application: ```python import cyclopts from typing import Literal app = cyclopts.App() @app.command def deploy( env: Literal["dev", "staging", "prod"], replicas: int | Literal["default", "performance"] = "default", ): """Deploy code to an environment. Parameters ---------- env Environment to deploy to. replicas Number of workers to spin up. """ if replicas == "default": replicas = 10 elif replicas == "performance": replicas = 20 print(f"Deploying to {env} with {replicas} replicas.") if __name__ == "__main__": app() ``` ```console $ my-script deploy --help Usage: my-script.py deploy [ARGS] [OPTIONS] Deploy code to an environment. ╭─ Parameters ────────────────────────────────────────────────────────────────────────────────────╮ │ * ENV --env Environment to deploy to. [choices: dev, staging, prod] [required] │ │ REPLICAS --replicas Number of workers to spin up. [choices: default, performance] [default: │ │ default] │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ $ my-script deploy staging Deploying to staging with 10 replicas. $ my-script deploy staging 7 Deploying to staging with 7 replicas. $ my-script deploy staging performance Deploying to staging with 20 replicas. $ my-script deploy nonexistent-env ╭─ Error ────────────────────────────────────────────────────────────────────────────────────────────╮ │ Error converting value "nonexistent-env" to typing.Literal['dev', 'staging', 'prod'] for "--env". │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ $ my-script --version 0.0.0 ``` In its current state, this application would be impossible to implement in Typer. However, lets see how close we can get with Typer (47-lines): ```python import typer from typing import Annotated, Literal from enum import Enum app = typer.Typer() class Environment(str, Enum): dev = "dev" staging = "staging" prod = "prod" def replica_parser(value: str): if value == "default": return 10 elif value == "performance": return 20 else: return int(value) def _version_callback(value: bool): if value: print("0.0.0") raise typer.Exit() @app.callback() def callback( version: Annotated[ bool | None, typer.Option("--version", callback=_version_callback) ] = None, ): pass @app.command(help="Deploy code to an environment.") def deploy( env: Annotated[Environment, typer.Argument(help="Environment to deploy to.")], replicas: Annotated[ int, typer.Argument( parser=replica_parser, help="Number of workers to spin up.", ), ] = replica_parser("default"), ): print(f"Deploying to {env.name} with {replicas} replicas.") if __name__ == "__main__": app() ``` ```console $ my-script deploy --help Usage: my-script deploy [OPTIONS] ENV:{dev|staging|prod} [REPLICAS] Deploy code to an environment. ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────╮ │ * env ENV:{dev|staging|prod} Environment to deploy to. [default: None] [required] │ │ replicas [REPLICAS] Number of workers to spin up. [default: 10] │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────╮ │ --help Show this message and exit. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ $ my-script deploy staging Deploying to staging with 10 replicas. $ my-script deploy staging 7 Deploying to staging with 7 replicas. $ my-script deploy staging performance Deploying to staging with 20 replicas. $ my-script deploy nonexistent-env Usage: my-script.py deploy [OPTIONS] ENV:{dev|staging|prod} [REPLICAS] Try 'my-script.py deploy --help' for help. ╭─ Error ─────────────────────────────────────────────────────────────────────────────────────────╮ │ Invalid value for '[REPLICAS]': nonexistent-env │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ $ my-script --version 0.0.0 ``` The Typer implementation is 47 lines long, while the Cyclopts implementation is just 29 (38% shorter!). Not only is the Cyclopts implementation significantly shorter, but the code is easier to read. Since Typer does not support Unions, the choices for ``replica`` could not be displayed on the help page. Cyclopts is much more terse, much more readable, and much more intuitive to use. cyclopts-3.9.0/assets/000077500000000000000000000000001475451620500146765ustar00rootroot00000000000000cyclopts-3.9.0/assets/favicon-192.png000066400000000000000000001642131475451620500173510ustar00rootroot00000000000000PNG  IHDRݾPgAMA a cHRMz&u0`:pQ< pHYs  iTXtXML:com.adobe.xmp 72 2 72 1 560 1 560 T+IDATxw|>?)[YewcL/^H! fS %[n{]ɲ-2Xڝry@R~ޛgl14PQ5.&AΟHȈ6$RRuvZ3QD0+{g]|fɬ ^U&cLDL]"Q$ LLdb-$HeһIP)JHm$A9" sJӻRfAd!X)N]8V Қ2[^̩fHPPIxI|os"`!aX }Ow=g\ٓU 4JFNc܁Q' Y=+6lcλXsƍ.t0X"RL:Neu'Gq_|\֞ ݓ; \p "f"aI(jޱdޝS;z⫫qԌHD6<3ƞ\.8f5( N- ソhdЪG%}\IXb ?,3G<:G~OoX{kR38ɶ#G3={zOV ɸFyB@̤aFݸf;o3.ںwoy¸1CGהՔl"ŞH .AA31bcCc<yNt|Nt [H'R9{ E$`9.mdYyo^yϣ,_Z8(iOHiAL*twuV[_N??FdkY lxzԧEyMCG6npr#hGUCvˊ2H$ H%.ff0tS$f)+^x9Oz''wwsH'[6uBNy$/c@4#-a2Zz 3Ks _Z`8vݽM-L${ GԣӈzR`V|AhoK$4JATJ V0 XwnnZSuO2"O|q-0s~vֺe7GNx< ?j&aSiC1fE28)%=W~z=q[9iHbI,R-fbMB]LІӎqSb{kt7]_\y[9 S> ۑd}C\@}5\2YX>2m7&q"Q^I{|A&Xυ;1oµFO@̊#v?ŝ/?ԣr$ ]~7'|d/Ci4N-(,7 n9FZ@ ͺ~!- jAҾrҙnj2:nu;*d!zJ0A=!uY?߆Mߛ9fDF]/ M:'xnp`t` @L~rعa5Kn2L31xH_iF`?TŚLf!-h}< fH8;=9|֞9q[_ٴO30 f|y>𢩑@9<:fl XyfLAh>߽OO9aㆈtcm& aJJ;7 !`%r5{pֈS3K>Y`sY8ᘉLWx㥻ՇKb=o$ ,3ٲs>盧PQ>P@ňRflt?'@`b2$ 2l DwtO׿Y5g\z/ʵ ԝ?:c2=gg[*Sݒgؘޚ7+R?xxYؖŌ?׃o#'L?Pkf[7A ,M+- iݸn?_Z%k?E:cRcD)# îI 'DҶ$$@mZ}x_ ȟ=,v 2]`a HXHx?pEgu2I # 3s|$6,X'ç-3u׏~qG3<0O d@8mY3Aq:qSN(x~*C q\ 4ЩOaHC4HH̚<3`uMnYxG=DZhWeA2HxwIe Qf[HDxC fXP;v0 Oi2췿zb9ng_uި#="$نӨM;tw{O7\#>c⑓ nv^4I<:mʄxvƭeCdfJ lPc konU׶eO+7_=uqyE´ hN?ȍ 9鰃I`HTY!Nip0ffN=urdj* POJ/`HiIRx x2bf&biH \xJ9VK˲ЦeXB C ȢY{k5kM)e/8 Hi b&MRR(͚tqR;$Ɏ'!5+Xs;-c*{ ͤIٟ?^lhnp'sαOvtvu:y ٶ;s{Q':':ѭvJ![i>T4" x~+R|ohyN=(Ύ"_P19`0+sjF[^x@N^.dޝ-]}㎜4yܠRJC)NH2c `@(c%vlkkvn4k4YwwHF(#4 %%Y@~qn(`Ȳ)*fPa8J*l_ʝ Ne%yS&|~Xk)VE<Ås˾soƌUPo3ORW")H(%M!dp~rɗymm3Ϙ4i܈A%\amAgX>ffaMoz?Yu`T9$.oUJhb K>Xw/yQI{DG@֭^/<]pjSviӖu>_y)Z^#wb3'yKcH7>]R&|_?Yk~I+("} x k4{d0{핣+\OeFOczȘ ;%\~ŚX[뺦fe}"W9EPl_s8h՛ǟ~fQenÏs JIBEuRiRpMrqzt,  skS>Wn[R!ùJF/b?Q R-!p@ڈ} ׫[s,.|ӏ=p, J–Ll0 "$RFRO$T ddF[Ԋs]hWl $h,ZQUe͜\22XM:=?7_FtS" F)=6SAt@gI)ia(mj޲n˪|hKc}CxJidg ۠xB1"w~v 7ON͐Hh&?$HA+Ӌw0 ).5_}-n^l݋ҧ3ޙ;s>ɶ*~:uQ2!X3 !|3_8G7q8D' 2ޝ[]XPwb&`Оf6{֞-==rӬ0;wzٸW?UkUUjW)m [Dr#Yi6ih(YڻVn0qno~g}oaaUS>fHu)*m=E3A# ,O&ŒX9thwGG-KŚ'!J¹l_Ff(+#'?'*#d2w2- [әVB4{0")Cs17f{D)`Mt%aIiXyԖ{?/df>g~'~* O;tpީ>(<(b#1!iO@O>givekY3CfOo#N,L/? cscw/ GU~_?}?eל]48Xtكo_nnKylHaز{OqE5 mYPȉb]~N`o=zꈪJ_Mr<Z4+?F'uCC㆏ K }~S+GB\('q#&.](ORJ 4CY KLLyM~HA{X@![YYl_=3'MxǍ`!qM,dfuIH0iگ\5LcԸ!O'\OI!)pJ+J5)h4'Z RgZDlTTo趻ޟbɖ3?Sx zwѕC:Zؽk凟'(/ )J OnZF Pf*,ixʳ 3(*%+.J8M]_|>~W' xկ~\>201峟7g_ee|6Oţ&MԞB;#9#Hx;;ɶ! XS+p\ BE  i&nr#f YO=~xwf?Gw5>N9oqÆYg۱jtƨ? K{`(<"`ٴug8;hϥ= sh b{_|dYM+:7VWgdC`Fش d PۉC~@Jk;[aIW캇 זd'ՄM֖y9mX|ϫJD;θLW.[s͢ [7I$4uڵǜr&$[r=!$C(nڟboF `vLßkt]rH)xC䑹7t!<,=%,̒~'l8iǜnêxӸܜ^Aqa $a$ͣjoظsMw=tϽ Nav3s_yɩsb6g7v˟ںIkڵcKP<62<2Hf 7m5ȩuȔnOJK?_],|%ʗ8dr>zw[-Xtvƺs7sN.**p/aG+fj-?5v3Arct b xCgԽ=!ѥR@4su O߹謋xn'3g[VR9fPᚭC~Mmx w1Er[w^35e_^tIM=Wr{ɧD2ݭf(!HJ@g1ÇDDBkOxQ,$cdcCۧ7vvtmW+:!5vqLyP$puܴR%E *(/򋀫K)AH巜H/?jWE5dԥ}~ݚdGGn  U!&2 L5dWǟV~੧9wQG}"$PxwctΎ!5ʪghFiihAI,\4M32(]xʴ)2ܳrr UxMe[#)g_&?p1uTWy:B >ۦ򢂔֚ n,L:hlko4Ȼl :(ڢ HX b#MaJSE^X!#+ϪK=i+}m.w8cG}h߃TڀXV1' |xM?gl kuv֮2kއ@Y㏮)5|Ș+K3sg@q~a;JRLy#.B X<. REnq< Jg[p rb_Xq[{sј LQh X cH{pCY/hÈ4Ȳu_ܼ7Ŗ{%]M|c=_u_+ KakQգ*^|;n~Oܐ7}˜i K4'_HS*d "C.c{22!kGV #AV0#ȆK| !RaYl_caJ~$aK^w-et/l Ů@t D:Z]aiEfjF~bP53ʕPqt +r ӭY ?'~经:}R~U Ňc^zQN0% sp>7_{yGHD2F)U*2ۡr ALˆ1'q:$E(% \MR7ۋ>W? ` N(R C(ή- j* )ںy ciPFC ) h­x_.uJ#2w4}9++?r3_,xͺ_|%գKHͦ8Oԏa$`>\;KaQhJRZӮ%^;3/`"*:a*la )dzN=vokc4P/qAQb4#J($ SIDEF |yyB9iTCTe~S!+b' =ꊂ Ia ֆ fN8fN$$l6Ls o aZ)VN~n"z,ZJ,sQ_U `Q^=?߶noЉI+s:%4[X9{0Mo%餦Vh%[! %4"*@Kw&&Eo-a/P {NHLZFДd2yRPL9dƧK 3,T^H 52$DIƁܜ\4:.05;'^ Lj4gPF1-n 1& (֡ c;qo&CyɲoyQYY?}I6u?xqnjΊ!aT&]?@WH{|0RHO@|:Y9o762e6~֩! @Щ{==0DQBlڣOU-kWoeD?|9;%N}wǓ7,̮Gdu<PSRݰbmSOZPj| 7㐭=htAfxk&}0)ٶ mgw˓A_q<,R \./> `N#tַuz*iq5>:7k{'}HJyxo_qaC͒t )\#Tm$z˶Ǒ`AY38F*-ھwW &Aśt>qW_r<)Ct^52J.9#а֫} SI!{ l>E +(Z~ƒaM"SN9j/[|cW4moo4]I'a45tmY BVKk{GK k@Jfwfa7Vp"hdvTw|Me( PG`RtbJ)Qx cѸ $Y' BZ1!L :u __h-[o'_ؒɆQ[sms^\\]3c:vQ8)fM*)$ }GqL^M٥^S/ege03}xThYz˼2^wfNFP PꈩO5GҡP"O4<&dmh^w-ָA5:ZW/ /+$`i.:j}[ʀFoW4yMlef![H$5[L,dEʫ*6,5Kb iXeukg}-K\U{1r 20RCiPrtI.8۷\k6NK7a9k,k! 흦iI 8hr;.rr\TkK͛;/'D&5t(ixs+;mQZ%"d~U~}Og&+a5׬7XK UvK{"+'vY3O8 iNi)OV9vq߯VVZU*8 H4'f<)̤SV(klj'!+̬0% ҴW h  I2R1{o{|KrO"I(OILƉDWӸ;:ow_zȾclM- `mnp#3Z; iL&79l) eȤc;s& 1ǖgdȨ,*<eLZs03jN*Avw֐n^pTFFd3+&hgY#R N$up_-TĽPHϞɯ}Z$֢aCfϥ>(Ԗ d3x}Hd)Hndt4DDq>־<'+u&6| \U Ȭ.6}MY^MCZCr<ցH%iTNI>&lݶ(sXzLՐK>ϧv1f.uX2gSL["NbRMboe"I]2DNl ɄTص| *}=eJmi}uQK_tH)XK\1%0aǽf#SS;5T0::`a?3_NdE^ci+^;}\&7{a<>1 wsQ&vW>@cG0bT*$A'?9+j,\ڦFtꔢAEp+n.#rРRJgtItR28!I6 IFO2kɘ*,@mv>@WI a$*|wv׽ #3Te3B C0]VL d6&:[`9Kpa~jkyMg(`}+rAZ' >Szn8SNѵSs`XIBzW`^h .r-aYsGy\E~I Inݽ#F4H8==oX|07 b";?^عcǝp6Yfe{:j ;WaShE:;زzUt6l}M2;r]$?l-uvu^j޶]dXeE \bnnN+Yg}Ol"P]2M+iהNӎv97\{ӏ~;~U9zظ|4Liv$IH$HY)0|> +ōM8f_tOj'NڹrE"uwH3rp^k"wz"SNJ̳~t)WNv~sිZ㕏eٶwbAɼϾ6=hhrHh |(L&-`U?ŖD23=m8}izІXu+ԜGN>4i# Sܷ }.0N G?iLnL'4K1 d|n\t 3^:̫`ƊP9\K )% X[Kٺt(-OI苎lGp6|/,7چ>vֱ&=Kslfԩ"l%4p~>lNwEy1֬Z]XA㤙2?js {α>ضL-`'B>GEb8d]vy/xigol-*Nn!TR Xi ~ö꺺3Ơ42Jpn:8эo~5ınؖhk3 jDkg,o8Y{'djQdfޜ5.=jI\ L\[nRCB$Hfã1x~\>gP_lP@\l.(ǜr9ǝѻO|_q,O@{CG͝4ª &)#߾g{˶59~xӸO@%ctq{K J=eKKku֚04LC157;ڕ,<{mݶ0L M !dS\W'58N t:yPR 1BDJv9= 5=OOJCih}'eé>4v8nw0;t\rNBEJ)1R`sTKm}fgHе~;_.,i 2lpQS؜ouyc3;Q>Ĭ yY7\|/[mmMHҬIS<.\6БW!gزG *7[Sy\~V3~PF')O; f@5C^B% CCJԆkAcS~ t%IcvS3_*+)m_<%t  S(9anxâ__ձvSbhцmOţYGrX9F>b7Hk;`O;_~wFL=`X9R()!LϿcfD,N)҂I6nČw-߿cO6 [xes8|X3@ӵ̩A$ҀDR{Z)ŚDҐRJae-}QRu8MO!M޲êd(?GfI \6[ݸ.G1cߛu#{!&MZ y7wlX54P찤NFr@Y}?;uTuv@z͝ߺvĬXaͺy孵u#KeA`яCiEԯ}(N 8pӖ)hlC[wwl\өmrrMhnk{%w m.d$b7?\suΙ6|U‰R{ԧrh?:%ZfLK(gl' wxyk٦ ٲc44O(0yY%UpaHFgWwHѹ75vv65|KSRZe=O>x5*oySkזk9:YR_>|a3o߰=/TKAgZ;;}IYk_@\63:+`/ΐf';z~bF Ƌ+ryI޲x矌;LG$.`ԋOc' fؽaMVg]rzAYxs#/ ;Dp,0,#ygwO,^?Pd]~9c:dXVVeaB$TYP !E53+N^|]GGK[ghڹ~G$߽\d?=ܓ#"uIFsSWGrn]Pks.^zTu$C&eZl_SuT";>]& @u$Vبʱ 3]qoWeyC((j2K7̙qG@J&^QTn]}_.^~Gu5n^[?s/-ue:[´MW{Ft@߱}ć)C.eϜ5?<yUܝl۽_ak2\ӬkY*wD`S14rrjFy]}+ox7f}/:`<~Ь4qf~|pKD$P*ʰ(XYSі<Q߽}u"rgͱ5~i6 ^r];1z5Uտy੥3W\ 13[nٳ̬*"ׇ0Sf i~!ٽ^ݿAh܈qKW-=i_T;lBNQaוt (MHh5,!ECʆL||)۸nO]|ZNY$N+=tpxwߺܬ, `.*lNt@pwwv.ߠH;Fp#A$)^4~gf;~x 'LZ[G_xw׮_ZX E"0@*Dᄚ/߾fq,Ok"b6m Dr~n@;, K-ZARٓ9 g!5Jo׷{_^|#FO|S.k+'=/M0 iM_p[Ħ1S9zJeȪA#= "Jt0JGcO?}oQW]t^&&$-s$IO-ؠμ//Y87KB6hPµnGlصKsN>;osVo$vaIQώ6~rD2fg^ͷ1y)縮 sPYR[7)7(')pW4>=zt խ|≣|5Kodv{ &20X-u ~oec`?>a|e9ܒصs7gy= Njq4-2 9鹾k=]|)Nuc)=Dτj}P%@\%s}3^VMU:n^^6][0>ud O(6Օ2ԽN]C )U]GTcg=fjw=3rQy"50LCڍ6nZͫ~6!_ Fd -K 6UTTEVfS8 3#fk6FrmM2?_y MT5?_m[/݉ 6lXkgw)}e@1X[h_w@bTI6Q_I ԺeFHtO-m FWUU-u-$Ɋv}zЙG-!3*Ny k+}exc"H&0k ϭ=8TA0orɊ_~9V$q hC'Br?w5q+=B6,M )StB wf?~]vVN:kgz_.H=)]RRƃT 9 g?3GXu&\CJiy$n29vlS]f>3Gi 4wڨ R..(|XɋMpQg|+u˕ENŁ@̇h.-%Uzxb QQ7EV̯bV7ȹdd$  Q/|`Āfb ~ #',^{W.sHhϓR2R+,S}Ț;늦 s2P 4JQO.A$̔")v4 J+T$#YO %2 ~)sn޾ #x0 &TŊ9z,ɁˇRc֖a7EoGcmܽg oHJ9M^N)7YeT_x?yjW{^K=I]}Ѽ`Ccc}ۮ;jE܍ X0|Iyj]X=W2tʫrlh oB3QmƬ7*[s0LqJ"@ж⃥5ٴw?ppO{a$6=p.> SIuV0585: $];Ջ%C>j[nwO~ticۂ|Bgиڲ)ey%-ёS% SqSS&ضP`Pyo>M9^DzTaܤALR.?go*)q,ےe /޶b[4DBvr 3ȵF\q;ۛv/^3|/Fi  -$IKVpg_~5_{wm!mcO,c2li> ;pQl?cV{]O.yWWˮ]?OmېҔ$°ѹgy_yų}AUIx 2$;'3G/!oDBM&iXN +SВcjퟮ+ d7> 8Yefww@$/TOTow9ťEIϑ}xǐDt `2+c5~ҺqBdstgWoߺyͮ5[dC[,^rT8ZމD@ӆLi[޼iK 'qҔP+!,>(#ޔWy-у?DY/>ؒ/j*um;{J҂*/W ɱYϻkNvgeLĽXf !z5gX He&+d74lѵ?:xR=$@MY'w=jha]V6vu(Gv]/LP^[)}F[Mx}E3 vltoԳϘ4qc6mpv+Ecw~Y``)GDyN9gcIYUgsrE\W ^PWqkg $L?p^=3GO4m;.g^5;~Gլߺwgs4)a^~oHm7tz|RF鴋|rP"%X `YmY>r3&y'?&_zyO=XՐJ/)ޞ/>ST !tC]sB?$&U+rW)>FzxTSBẢڧ?\OP`[k?R `9`dI];2ϴ_ ZF!UJäֆd!mHޡJk ׅ}+/$酡քgɀ_ `G "࢒PeUp@no~rÖm ^zÖzsz7KwO|˶|5\hټc ?3z3>#W[lؽd+EdWKJ?G0`k WBXԋ9f>t-$ɋwvk.IaiC* ]jFz/Dg[ǃ?Uwe Yr ;S{بa U<83CgaEnH:(=9mwF fBVTevw%::xİ1 - ]?&"D[4{nbM[o}'9|𐚪P~dW{EkP@t//oѸw~ao, "}kB T65a>ԖL@dv"셌W;kYiÉΔI%FMb55\닇4iX HCZW _1Ŝ:G9fz@AĆ "jE*'Mb?n^XQ+r)1dh޺탫9<`e#J4@|1Np7AcW/i! f$t"xgstc~L"0zRdV. k}z9= ³BכHb`M{~f:A HJv}Mn<ʼn5 D CV/Y #0!C0CB fV; o VUR~ǮB{ %y]C7|Fɵrrl\>dȄ  Z9yO? APqҠ~x;z>$`[]ۜfHfH7dQ;nI1[ԬKH1m]Kl_Vnl5@` H@^$˖K?=a6޲fO G=W>8#'/Ryee2@ĤL%ް^w>@(b5NޔeGtwv9׼3 P]9מ߾͔fj!aBBN.wiN w5%v,D]`gγ<Ԛ |r%v7~ePbBRuZ[VK_>}sO'tp~/^40 ad,hpbXa #MhF6$|g뿟 Gy)-rJnkJnY/&l{Jek?fN.=GK-.> oaHpڅJN4`7?8}<{#_+_{U8ZJIDL RU_;wϲ#dO3Py^*@0''Í .z44젫\>@hc,<=<?!KyލԽmko xbM׿ʣk'&`&ֆ7V\z};6lutǚk$b vnj 0g C4Qq<,ߨCX nkdK c꒩'Pvp]LZSRs]rm7\z䉓`sɢ_]]YziY+CA}/}r 4-.R*SD,,@uSdgeFض5h¨3_?~NId@H(V<#](n[x]9ɨvԸNI(t}¬|5b֤ sW#Q5CE.0k( |gk)*$UL ]2w6v$;JCѸ24XaIIǴd!K$])1.Lcw{CsxUmܲb]Vnc5{õeA'E9vv^}e3?XFkCÖeK?jXIM^VD<Ҟ![rEk^NNV8K&__-0񵙯WV}#T=FV±*2_|)w[t(c<=: d' XJ̞b!;<@~e'9 !qdғ0iI9I'":5'ց1ltGsD`H 9#*AYd<=Ӵ$u{+:hW"ǟe9m;c"p=u)U[ܰF{<{1%sW>fZt۽zODog^jM0yǠ5յT .9H{X.gJF\\.P1&6IMt;TF8a9vs8BpQc `͞MC*k8a U" -A]U ByӕWui۬k{o]:eV--SU#h+E*۵蜵[=sSce [,%xzK7o4Oŧr% 9ɺ NDlg=7j'w-uDmLsw|ljPUU I.CL$yD&t=| 56 (A@$}҈'o~nV#欼m |"&ŷ8${8F$"3 5<ҥz@^Ⴠpg(ڎGM@EX+\ B$ʎ~e#` 0 Uj Gm[xM]wePr>72yfY%+o-%%Բ+oz ke|5ӮN`*v[䔟6A=I0k/1QuҦ)q,nD笺؊ّΖ1-,,7pKC-fo5A5kz![HU6YJM1f{"?Ep"x3>ܛ/"qz fPowi.J"41UH"S X p_-KVLbBxX׽{,?e)OpG|~c@c$I@$`{Mυ$Luteu{3ox¢0+1[U"cS>C/Ce=΅B[4SRFcz BޜB~ګvKJT a8a, CFm{9ӗ3fd+ TV,V);qiLP-f(88_Ic?'ǘUcJ+an⠾ Y{4^k߹;,Vh`?#c`{Z_ mN!>;}ﹷf<|6sq]Umrzʼ?~1/BIǽ v Q5,No+Lys CУn}㌛X \ݨO '57a1EK"(ً!乇9XsS^AduT̄DTt01΅h'@ !jDZqֶVeA':#"5 kԣWϷ~^#F '2L ~}ܬcq릂f۹9zF% /-;6yM^oߒݭGßŏ]Nol۾5≠09nJKtpD>[X!{3_0oXȄ9,Xpf~JaF˺v K|LTIuէ3ueCwـdLfff\+CQ̹Pd1[;/)Jcy1)$HJ ]sZd1UtMmh4ZzfN@ =g^zZр`, }fK^pѐ/o<ۯS,rD--dA2aDU-$:;16HLuȆsƝH0@2*Z_V?j#>+#a֗4|% 7VW[T :؂ILQ\snr]r_csbbqt0!!K# 1SB~DsL"HTx(&Tٽ D''3FHG IqvF4bڔێ8V)q`rNj! 0(m:meדHq)*ͮ`'!4"uI\pRDUG\O\y۟ŘN)I9;mxc7Fx)! @ ֔ʈ5:iC2̌ A ɻ06ǯ2"Q_ 1JwschML0458txϼNIl %1uvJjVm2)Щ@[oC0Pՠ 1\[e46Y94=^.8\*n{|s^0W;3{&F(7y0 &t%َDtfN$ܠDBDݙ@ VZ?x@ݶTpIv٫E҄a|ZsWUK֔쪋0dmcC' 3cۜf6D1H2eIB9Armݼ4MaRPzK6E=:61[M \M\ǖG'St^FYx#?fuLTe ތMVh$lkd#r!@D,y+?~3~crclsqا=pC4 !=1$nvB@)&X@n) BDF AB {]I)L l?dIZ8$K P TjPk˵]ذ!IqZH+6 & D‚Ƙs)ni5(VH-QVmi&T%=ιfwaғ# S9̜g6,~ K4`!ahg̕W^5᪤ĈYYyŅef_:nt,~ߚ_&9m6b)6'FG"mLl# RD,So~WX1`OuSDac>_k)2ZbH~Ci #3"B @8ũ)1fQ@zÉ @u@a p.@L5޽rz>5;6|Ƌ/L0!tC`LK'W 1.QOEc~gK,- Cj5 P$EpA$JC:!  XAM3Imjґ nqѴk'*&~gQL>KE1ɡAL 8i.f oz;t Y(t^mg[o,|C'b$a0`b4[*K|˂pov1-r?"E!@`k[}fޅPGHƮ#n_L'2^` m08zĴZ0elbU"VIU5@4V}\wxƕAGxIk D5'3*v}fHciXml"TrT„SuEV Yiʺ7*50%c;&&ʌOH*[7[K$_BpPj;dmWodf_ )t_,]rUK]']9r,6բ f` w^g̽3jExaʕ ."iGQ|*JY $8BrPU`#!h0x"d=b)YwL ;1N'JM+XaaY1J[tTڣw+QRC:|øDWJFLl|lugp9HTe3j#&]t{lLXgđՆ-H\p3# ;`0dQ怤Ccà:#dB˴BMV蔹jE!:ΣnD;x[-9ceךeǏ\MQq ϚE|20ʪmמC/~1@Aç6w^wϊ3HT\, ~a e5atY6]e-JW4v!:p.(`pA(+޸WN^ WA;.,D7&x_br.$c~9yI)qG8qd8?z}/Q_v5S suθjH42Vй$˚01tFeRUJ%]7 X j8ԵȚmG}=? Aͣ ix'Z拒};$'@8\$hѓ?`,iҽ7t2X 8(` BX`!`Lਭengd"k ,. p-`dDD@h pEZ>Ǟ4鹼0;Rt*&o-QyRG@LU5e0Bf^Zw}k͗cTS#G5̹ NàaclqKu׆C&=5$ "4U@]u'vm)ټɋub8,kq7W6qWd%P~mu?  z߉Ww5$=#fРGmc+Xt!p8;t@btZ!!0!`\q3'/fzs~L89q"cܰ}+}흋ƌ.T(R^[i$fdAi6CYh+X]333K8`qOFX`SoLQE "2Tהo@!]msY->W^Zqu ԙoȖf? tT4"))`@ k^oxuձg i0`ڽrerjXs)BxSU46N$-3˖Ppy%0v6bIDcs0֟b猁@X B Cε;'nkܳgDD蜓cў- kkڪ̜Vr5o~μHn6J?OBf"0 `P]_Ԕ*ph#\{ʹ={v?[׺x|}Ͻ$UԭSvRlTFb\BTTLLa 9?ȏ|;Od"$<5*7+%3#&:fKb)`\Zؾ#c%C<-Ù4}>2BrN|C .S!3 VX½/wnͻ~KG7z!79%ZH[%]'7j%JZo>У;,:mY/E{-Ũe61wB0McQQ|mKt{bڲ: Lb^2\I@<`Płu_~9iU)YsǪ'2$^I.κ 0}"8﷥[v Κح3&>990 *HŚcmӒӣrr]`Ll8h\z0a 1G 008"`%q̈́AZ4YeKD (زfO}lZ ;e'$Q"Yj2O tX[]k޾}_~;km?=/ YX"&A1@iYH"\CQvJ!%,@hLbhםO6g!'Ip88+A3MW9/!8YOO$ #'7TӲ6A\0D1?5mUsug*.R|ǒiuǟ~%bbuy,̯aT"\^Jl c2]%IK`N!Y# 64]G 씕)*n߻mæ?伂aE٩v=11t0DuUphѲ{7o&SW^vٛѩWܢ,kM aqĔ_6ܵ.զ+5'DkF(] dSR;eg奂" 0@~n$0W5"PH0MzzvLOKNIP3O/*zw|+F@H($R;XWrM5 zw`ʪ<уyitm3mmCaIAx]g%L$NuwX0dE$1Fs%+gcGF U(1FH6J+oyUGK=R5tؘx}Nɶ(+G\3 Sq}5S=ϔdpQƹalWݴl׋zZ]$-%FVh8˜Ж_-_a#{"LvlwdkRTS]bnA%GJ*QO@3xx´(y0D`*fI Bji]pjwPebǾ`Nc] (…''X !8c3537Iӌ|)D*Hew(+Q0ٮfG3Қm~JA"BJˈŀi 0Ttθnhf .SEe&cBs,cS!3 G@%mU:sǚ ]8 `4xY#khsHnp0Ffq{% <{ZcxL.] lQΜ8\fG'6y tf q/[}>qB10Xp`@sC0ASEeZݯ.*0z,K6ҪN !@5s+|K/Թ{kk R[0ǫ69DC!LRũ}ÇI/,c#Pj]ƭX_QZQ|tώ}ɼN 0n؞=:^ )A D'x}n)4b Ng Le)Xg @UU :#*^KzqAQ!0F砥p9-;κ1Eh0+s.#o0S [?xڜRn| `H'\"{*<;|kqI1]vQV჏.NJEr*SA?P=`rr97%_hnE,Ei(y㔄#T'^5fDfnzrV&zR7A !x=2)BsF($|yy})+"}qDVXnBs,P0)joϺa({ ep X)-n<| :  NRT!L)7+ VWU[2^lV,~]lc 2NzEuMo!Cg3D YjZ"Փ!EKS>1#׽>qYr:Xz󵆡;tGl۶HY=^!bmr $"l3\#ˆڕ8{Z\jzM{ݵ5u%e c9<$]}C&Km\`u~}ч?2kl=cSr:tu%Zɉ82T @kX]n<ڻlq.a` SrRi* \]} Hm7)"m+!HL4DOVX."+ǎ{z.`Ϋ{~Nȫv|5{Vݰ|Ѹ5p줋 ӽGV//_ÆǭRm7c,`TԕE\"Q(VUn ܲb!j_fx?|ǫ,j֖=Vܱÿo7ŷwzѣ&Jvf573G`mҿsmk]s?@NaM|浧f MKIKMIiٚ*BHDTFBV \ LEKv#wn-^p'l?PGw)Dp2͞Ma(`  GuIVnpP.|`ǡʧ= XL-V<xjMESJ0a&T=suʹ{YGgLc Hģ"Nh{l*›c|ٶ TïKaa[@U`?s:`/0nhXwdˮz?pּg"Ԡ3)v WVmJxdƝ3_|̞`z?͚}KW-\w_z_>wn-+9x!X4LzmRamLH BvuhQׁpI]M',u8zX o}UKjZTfQF$W (n~X"XK({[ u7GT!X8&G(}9=᝛ "­j%e= T1\ԑ>FjE\7I䚆H_B%JMmę9BEP`׏۲d%cs%'jy sNux|~#|˒?JOwZ㲭>5/W}_Lz$3Uz{o.w$&:?zi iW\~:(JM+-N8:?XzU r g}f]jNTNzծ\1Fa3=ޚJ Cp@ĤC@?kn0Y,vDF@n԰7T8;uژ\ZZtr5USubQ4]%s]ղ+83>nNEP&Y˫_#@e(N >i* $SO3h?A1<#'Eps)# @\ͫv ^B)$v1212p@M~|,pXk !VzY2םV~< i;],.̑REs^[2>?-lXrl޵Gl{~EI=mˎ+g1ǠMN{,M{'|<燎]:~Ν=RCc&N io>zU%w} laE90c#jA9̹p~aXHC`j֖ڹ( ah$w{boWBW k* pmi`ۚ;֬:C\H.]SC xs*Tf8Ag4T!BCAKX 1 k|;͢ $3 ƙ ĩJՊ1`LpB ?ļablcDTL:E2hduqØ3nW+ڰkcVo@(~}O9S]?~S&OkZ!j:v? zxGwpprztlnj\kܓQ`X[nh%{tyw_~l:0o}T3+>vO8\C9iWgcl{]uS7NQUxx;\WwӬ w^Ha vv&pYMvS)55%:67UMj@U9dRwEidUEpΘy7,@j0HWᐊ0f\hEgA?Tmṗ:O#l@ɞe, 3*жSn?΅=߼7'TUݴO?R_|na93qW0rT݉xt{o@b'ʷ_2*W:r'^gHWJsR?HIOL׿CWpDD3cDЩӟ?'һ3ׯuXҪf{Nr;dt,wb*_ ];/02Xsܷo߼DtGb~=wDT \S$)k{ԂԬboCʣH0C Ŋ6օ F)-M ^\u5y c}YŪ#J1>Oאn_9Y1] YrڦW~c lˌmnԃ l  s`~z y 4]cHs{!j+;5uPY+yDٹf7)&V*KJ۲oÖv& SRYx}MЛ;?TYPr wauCr8`-Cjlr2 ŊqwۥOzAibvMA]<ۂܡmoxؚtNX,XlcK)JO}[^ji1庉7\t @ SJ3C`A0p`dERt,Ce[6.E;V~˳x$9*+րvOغoOb]G:zT[}i y? e-ejlQ Ez߆$#3%=lMG:Gź.uR3xPS, aŢDWk߿ :ɖ,ª.[`CMʼna4Z ;W-ypYi5duӈPhfű-ԣz͟vFNFX AVbw@˩ne0:olfaFn=C/WX[c[a)\qX tUˊw|ل{43;k܅sOjA^k@U{O3sb7eu~t[ .Ȃ!` .<SQ~4V'X6 nU,R2MlY_K~\L8.΁~Of> IsWVWF;6-sTѰ뮱aALMGTY8B Q1oK*Nݻg럿eה4ݵ{Kos1B38uwSW4rӳ͍O-Kç?*^TԵ06>V"D865?rY_0ĭOHSp~Md *65+>{?P[twqrB2pȈ={ڝ1WHb)޻Y_w]"hGw>k+L!t`N*Z-ʖ+pL9Fo]n~wݑ=v51J74 S!aec͟|Y1'%P9Q;+<;F%,j9P* /ۮ-I,u_>{ %a&{ۣ{K]2bا|}+S&_Z'ϼWLZ|͗_}Yu*$8P,(+Eg ޾?[!uHU?x>$=)Άڊf~AޣEIKۗo*$dMKv8튅ā@ֿpw*軞< Eewl/1 :]2-OM˾ztۻf/gxvFx !>]N Р[ay}zp4omӱw~ O<:=5jt풟t7}W_ȽJV D^еK}iy#wm%8ZP@. DeН *-;i.7(ҲL6V/Z; qZCWo4| <}J|e'f s׮}=.>TWЃχ^mzJu T02E#NE عA-"A8X?pUϯ=8wޝ\oeӖ];׿ˮ;`zIpڍ?-/i`FXc*0UqXQ 8 C&ys`u9,_vU-Kޭ%crn.+ח:Vs 5x %j߯sP&L,ӌnS.Qh0V4744ݰ(,TϴA]kC> Q0KNv%&'IN{f^~}|زkE8G| X:*ڻK?t%#ږdPVpSg^o8&O{wok7_f}%'%-w ^;G׭?{乧nOJ=/ YQ" j} !bq Rr."Ֆ5̶wLx7t@@h!CҲ{яZ{?d>r:$źfndRr%xPny=T1B @)4}p9duԌ1'RB\MtMP('VKN}q=527zD$‰v5[0];6A@A6+A/d aZ[]ҸƜagm/9]׳{hȕ F0TUUv˺꺝q 8c>wtF43 .oE{{jsz]saDr^ θ?/RY<1϶G77S_\z!oA''r a`,@ӿ|޶`$z9@B%u]7/?1~e)Ii)H=Gs吡Cj]WSoMk7&ز~wMU/n.!J"$D;#>ě5G:yX^8##?7:%Gթ1P|duYH2#]?t5_x{טY~A5DmS`\$YBB `'yfqJ0nMq+< o胛l=uCG/& FoKYnK?B}F6CCjG[˾y$ʾq2z`V )QjoXnNڣ{nݻ$:/tؑC[贂믺ٝ<`Df=VzoK`1IHtF^s#ӏ Ak!}U?͙pղBc[1'Q)Y 1>%UoW㱃Tř8dXQcVWYv=.9Pu-L MMIMqi?|8w U"9c0H#!@gt"T6!1 5i'`@ +pU(cvb F2eC5|a IwC;FPOܻ0O#G7~l׽wљ )ݪA =PUgR4Lc0cB t;qqCzyAx~z-/4 WuٜoMw 1$B ˚[ 5u5C:W!g,v @!WqqI'N8 \*'2N!@ x%I aWC-AԬ/ Gܹv2;g'37Kp3$d$d%s]S|_߱gaFUq912c"04 fPV ٩Hlvls`SPE#^99@.#*rܡbR-ZX)DoOS]I $qa 11&536.Zšw--p@ىļ%EA6砅 *82edPw<Ô PxUF)Gܴj1c/$;;+jz^3HB`,Q B>Xqd0 P ,;R2r:̸8qbܿk:.,͒ʙ.@JJM?ϝM?vd5/zglp˃v+opרhA #]p .HX18&HRB2' .bʾ D4K휭e s!߳|M((hR{CwmJwFf:SaBp :dYm6#[ Nl HF".O<jw@ "Z-J?Ad$DBHrt|j~AZ>zhэK)cpzge6+D0B2:egtqو{оCw1SzE]sbE012ނ㈅c+-#=*ITE' iަJW~1Ӧ8oK㏤:,1&vƝغ/s rq&!KX (%ɱ11v9B8ѫ[<%%qM>yLuǃ77CvPo*0cm-w3"d +OzEo.|jwk(vgR+9,N_-9Tvx*gTX3 @D d'YOF՛yW'>'eƨ( $Uւ̎]hptZ!S^Aa|ECWn|;c!{UiHPCv,ڹgat9j~i:duֿe׎)O 6 ImYEVynnŋ.8fIO੣6`(toLJQ$!keS墥_$8Rf%$%Z%`aώw>Sm3ڹgi<5?^wm(.-RtXm7")BiqbYMuQF +7wDb:k~_P_WJ/*+kP=>_]C]eEu[mBZ%ȡL1]A@j95; PɫO~F0v&4Êsiad)*֕.߳вk-@zop\@@PkØ׹6ِ%l\9mFk|+JaJ*-uhIe'xetkfLJCA@@[ۏrI@ڹ+N`ڗE?Xy&x߾;/CI͑azu*8dPNFJrR"< }>lڦ>sSbR2RS8}Z[Ss2BA"HƂJwc/p/|i 'O07bs_8@tf=&fM#JtUMu캱{ujQ.y= 8gHpdjG k4TUUʫ&ߛ>sYL}@/6z>iYvW#*6*k븎$u.(^[IFcV: D;p c6X9v3ځ9㧁`[ SYWsH!-hgJ+.[gK'_6>@fW_,G>EPL.EΌn_|I셿捈wdZڽGGq:ɍi8:Š3=ͮEu)D8!ɾ~͚1:+2EcG~۷AD,=&]9,?/MM5ıۏX:N/\C4z q AHAr LuaGV,F V;2}\Һ.a >ϳvťu\g^z#vxzgO_ۼ/S]!z:kWaǍ~+F'QX)!K8G$>]QM?WHPMSXXsxܾu5Mmޱx^u5+#gL3ʏ 3F8HB1TC@b(RBR48ts֎D+: 52aF୻"rQ80(@`SMɺܬvcpw2sÂ9K/v9ub1\r;mG6}zv̌η'bjDCA6S"OxcA6[;Ԏ#c*QHqepUBAdFCUS{vin ._Ea?ꋺA ]D:rǎeu,~moHԁ{&J 8cRjDs34v`"әPD2̀MJX^Eo?ߣ{nsqWUgvR }/"@Rݻf14&ނy؆K!q8(KHu'>} ?r@nJ=ڦ8l.D: NԁM=-ހO5徊J5+>4߰l Z?b¶W Hy˷G9`Xg"KL7H[LO GYd cjRnZTUæBp!$I7Vwu?)9L7d~ vB`c3kԦX0!α Vj'? +Oj8]f$Yc-D(#O6{[P#L,eIx].&j[|MJVg{Ұ B>_͗ny+v+Fo/ۻe¹Y׳zֻ&ĥ!IoҖnDaN{G*M!@%=+6l޻hvԈiSv&_p`y[+A`f8*˫RӁhkhLqa@C= dh+ 3{dv '7q,aU/-y[PRFҜ?$.=Hc& S&::WpQ\+[=Ɓ%Iq(X1<ƒ@;3p!D9(~nj{geò b/[8}rcOW|+JWMI9hӵ%fLp8~mƎ%~of0"VSIb ԙ5V]hw@.0 iW mݘ?!3IWâʨҊ]V@BrtlllXE#UBM`L@a$8sB0n5IEv%bSKxz\(V97?F=x3z )aDžuen*V7_F|L-Y>y[-mpEn~b#3)קGpqALٔ C}C(iH*\ kaW#3fff r~QYMu(|v~_Vu^Hût[cwڝvHq.g}"a**s`h*)Ȼp!@H0?O3z|LM̈8 :ٿ-qormtt1 /8IǛ&?2(/RuPnu[& `*Xɴ&Sy7^iyQ]{wꘟkrY,,I=8g<.d:p=|=q4:Qj\̯?cs-2`Sg%A {:b`#[5kkR22\5_~~ Cϥ_bc7hFaKX8o VTuqnE\|˟ ?s좴UU՚ܤ buY.V۽r+ a))1Ψ(JB&#W $o 6Kܕ*cS;2eL_nj͚->UA~B`(.hh lM!y2C7x@7ohuhD8> B9C`B,vo43 tS魌sPz$[rlr xVQeUR ٿ'G@abQsUAݏD 1ߎ# p4 P3nZSW+!qU] >R)י%{z^Aou4*'s+֯u?ʘ#o)-TDЬ)~q P\v"ƕ,IDGBf/Gr9@5OMjC`Mv8-QA;@c N|߂T"} j @B&&T\%֞ѩ" "Nc<Z r$&@έqʯ`$a΅j'leMO^4{7L&OUM݉jN`Y)>pbҥSoHr)"QJNaCk.eqf?'38XcSL8p L):xp_\3q_ #faB/31 S(&TfM6:Շ!q2 LBIZ 6U].0!܊9-byA0"qMe ϯyz0CACS/tQVy- ro5p0pb]pVcDˮE9R0ƨo\u_^6 !Mܼ7"wf?7^=NBEG;tǿzF'L3sb\C_-btq@b|t;qv9p. l> R[  !^Sqq !AQTW,IB"SJ8L0gaN(nP1FJ %79 ~?/R aM-?lk:/.WRST( 7tƮ#Lc?uȚ@`5IENDB`cyclopts-3.9.0/assets/favicon.png000066400000000000000000012622651475451620500170470ustar00rootroot00000000000000PNG  IHDR008gAMA a cHRMz&u0`:pQ< pHYs  YiTXtXML:com.adobe.xmp 1 ^bIDATxw%Wu'{W 7wAjVNPA 」8x{yy8`09 !!!PlnVsNUQV7?9uϩa}W.w 1y!'zy^=_=ӾU@I(I B^LJC%uecVA(AR<3{S"H4Iz c &(ϺKSee!D I$ڝePY5ދD""6c"Dl!-B$ "3 UBFzP"eU2rQB6I?FD"``z@5@88!( lD" &8B@!1$F 6P1@DEUC "1@QEIEC$ׄd #"Ƣ!%aiWUQ̚8>{ھqa|E/>kOt:D ~viz DYm4F]4z w'Ҳ 6z(Y&զ&6Z- `SP C֑`fFU"UU(W(`4 d,dSXBdH5XėΦ R"4 XˉCEUZ1l 1wYU5\LzS" 5>y#e蔲΄u+@5;aO0k1s;iJ 4PUjb#bZ-#|+18i- 59CDTKJ-fc@E JDpWŠ%C!VƔY-b)J4[ɭs֐"R!a H1P`fپzW:D3OT1 TŔFԝYOZJ]= D `DmKL]g#(Pk-|E&BTJtŕ8BZ caSE(C{q#Kߵ :bqy׬qYZ9#&$-Ce"mJᘚTTA`kE 2,dx̒ϕ{ P0Ce>sO`_:{5H*QTPP3Ojl6e@"]g"!(%mK5*QEPׂabk—&,K@@h0%HiSZ@Z CSkr932n$W a֚2/jMZK51Jh9ac'&{z{aL;. Sa,bPR0Q]6ߝO=tߠ$V1fy]N`'V+DLl81%>wIB05>^\-cDbs&Q̆B L? -qҺkP 0)SJtJd>/ce`$2㋋M>(Dv媦RUH*EY4K9%P1$O:@PRXjOL#8 -ز3YUPaa`ٳRHdizz Q3FOToABnAh&z.iN‛9% ,Jn~8Euzf:=3(&*WK΁ 8#htfENȒM aJ:9 `N26)!BV-՗ \9DO탦/21ֈjmA¥TF*b&1T–D 8j3L"1 {S6~VP^ny7/zRRiiF92UɔiP37r/e kU^\( K$Jq*mĢf8 A, E<(4Nf=䚠E=q ſw}YTs[A$:O%c HHw+7F#j JT0+"@2/UAlf6{F,hJ ~ƌYԠ23c%/4ȴŊ82hi(55@G^B"}?yOSf.F WYҢ]VQJ QAI>xk)"1(`ֱ‰y/d;a@tV)VTxL (QUs },ߑrbQ2(d1,r\IUd&f3jbtbeII⢈DQ1%U_ri֍;f"261U9k1|0l(!lH r0KD;U TŴe6~MAD'|Ot$&ⓄHh~/o@G1+ǝ_s3PFk -Lv2E/ցҹ)"Rm 6*:-rRL| b!c5IT>MHLi-!kZb!!65" &cLjXieuXh!+Mu^,ԓhWa@Z|AѬ,KZ,@X6y\ % )ۆ1?uZI bD2tI I QgD6CPĂ >*8嬳ԀD+t!O\fִz,oYx43h3['I=%3I7s*󎗣Y# sc*:]TNҙ:fD&6a&0JIEQJрїy(*lK,1D jItԵg2NF5A3<%'sniє'C"F-"}}W`ҷ)dt*:WZX=3(C"(BE4@|o8 \_T4>լ*QJ`9AEslP:&H6C "Q+*.ˌ ݥd_Zkb-[F0U-1Xp2eyGd:?t 橚>ADPNF L;xzqT^|v6"s>j·,/$}Ƈx ٻN݉<_NO^l֑JLi-#b4ETEa.qYUx* Ah' T'Zv6f8Oc0<+GFgy|]IiN'']=}1 +xOe,X/z"xxo 1 1Թ4b!b8`29!Y,T 4궂R}0>jZQ( 8Unw8g/T^woV=7*QDf&fb FφYC! BŦN#Zx1TC%u+ (g̠J gQ?s v)R7S2%φEH caUl+}%Z$Ja 3UfքU )m@08!5*';1|8+E>Hy*@nZ!($9xQlMYITX&q&!m&p.&x6\$FNZDL[&) -b*U?Jǵ<PuPz Df*JBlTb*r;nLSq5 xꦄ JunH:vG@d*[& DX-\lM+ڲPFKyLz@Z"T115 -q iGl &Nk"Ę2("_.H S*ݺ$* )VƂh{s4'>1k@(`2C(e}w>hs6{%_λK/ͻ{oCKFy7CZth Ll]E]g3_9o{A'+=GZ(`;e˖&4+q `R,Vt1jKQi4Au1i5UQ/P1`(jy'"4dIxmӋ+$qC [(#(=229]w־|+WXꓟΝϝa=;GZVp֦o>C/5jɐd;u?}de=Qk>O=|?}~.&ET}jAHֿUA0i)~^+25c:__@SNaL@D8tzfvyd*}~Ɏi8lX%Fw[?٤y:R򺫱 I,N%Kb& bY`tq%S}j )2y82Fi2.3'M'25Ub!%:+fT,C1D&|.̉!$#=C>r׿~[|gw}\|YW8txرsHIn۶WUJP0wNPX7 t߽;`UޱdRE;I,YON@>SV8 JqVԭR) 4{}H߃t>8ʉS=iK}GH e霵у#g=##rmlcG/]WAc:ee(,`u?ľN/TF' :wD2e44NU*i`&c؁Ȑ(L,xVs[p|_]|?}ܿ##߸"/KľvUbCUEN]k5t]U qѽ{z68LB1IF bR~1Z-=cQz޼',XH caij AQѱ217?l)'ZG^xO6SH #1e5SB32v:)x T1iF3݊d HG':mdz#$֜O}G9XH[Me^-v6q6xHUtʺ*CB_KDCر2؂kӿnʍo'f32^0N]u~ʸb뮽f{)|ٓ]+ oyk @ E(kiwf(qbg_β6>jv9>:xѲg>}?nO_vONt&y衧bJu"Ib,| k8\cdە*"9 ak L`UqGF&ly'蒋y@Q"~ -i8`yyɦQvO'[57^iziG\VC3~9X)fQn v`>hT$4 6#%+o=8$T #b&CD H\b O J9UBlFee]{+-׿W]y-?|>+_u%=ƕksv6C>f?o?} .n4[3VWUE*ѥQ8] oA0h4=wxx>'G 8OLmZ@o0 C|3Y,snT55P'NHE\Kտ`QP(PV@ 1v{r5W\qu{_wڑ%W_.l_~9睷~Oyن+VhMz5g]pcn~f߸Y'5i>:Cɼ}Ip$K\M - ivhV`*<ԉ jhn1DCv>'iA'xIL.G<6r(* CDTQձ! J0IRH|f>Zyk_qR@,@{4zui̝.. @"hx^ OHYLY! _͇G7{Ԝ, cf譱%AUUE$B5H˗=z(s>>s>ɏskKgږd kp.ȲZYY-+C~u_vݥֹ(}Ͻ'Njkۿq冁xfGa R~'>/\v.t>$NNh>;!vn9&hsD@ZߦMPn؇YUFwNR4o>(Y,uT4y;gҧ&AU0.˜ !ttݠs7`*Edy*AKWn}_X[O۹"\klcY}FS;v?|߿ZwݕA&E&٨x%%fPuf^pB CH iDh 0C/ϺˢԕlRUt&9(`%b G>d[wС}=\}jGҎF#P*ѻ)ԥ6B Ȣ%$F y޸}Ǔo]}{sg`uG/:hni2fZ"9jV67J($85?z#.~y?{Fq:xoE "0t;^Y B aꜞ&bi2 0N SNh_ߟ6Wt3EV$YGc ublop@d-=_ݞqV@EZ݉PϏ_FFnYP5 -;6y8J^xvρn\s_snt8Ө" Sw~)iHϻ=m;m6 p)~;ߺqϾG><4شejjG;@P[:*L(x^>r̫g~ިymW^e^|]7}%Wo^=21>.@ u\t)ap*o4ע(K-W;/|˻ 6fջk \b0t5. c q{#H  gFOը(ί{X{<묳~z{ˑC:SЧENw:EO3G{Aj**Y=viԛlJioQؗoswOFGGFI`}=+P|KLT45߹ *JSjDbڷ|S֮Y}>/}VuV&jev XB DJDI # $MkW\űGoh`,MpZ=)X,qi|)g=QcL]B:gʖ<8`L!G9Obz^ACv ;;w>k/jt*{*H Û8yMo;Q Kn>?n8g cDBj4IXzt&|4pI٩TdfkE꒾zvlTBޱ̧?{k-w[(f[۴?=;pӕO:;0zoa}e4%$ D;x6vl;ԚeK<䶨Ib3gb N0d!䆑e6Cl|_i=,ؾX0zK$$ $(4QPTLҐ KExk0C%VE$ĖwZCK': XȑڲvUg $@DoqD),QNt3 'ia| :  nu 1nίo/}- \U*&b"%vѤT &1" h)!|>x4+ Ϫg}O Ԟ_퉝-#{|_hlں' fy!65RԠn|Kږ{z15K0P2(FMY(bDIiQ77~W[g΋.pH솋)O|ئ'}Yb 5ل(%O~ʜ|4A,;o;MsFFE[ԚؿvI9xӭGGG^^npG:i0Ǵ.k>7Z3[; >w9k -Y0eZEa!:W~8_k$z 4׉ *^Krdtd1~}05+WpuW_}=?*ƣJOIJG&xw.^_2b y%Rx>1:O=/5C$͘BD Qb4jGjl6??|˗ܢE7Hmǀ~睶8<26jÝII;đ3Pe XgrFghg,ӐUT@g@`۸ryG|C)gv׽]!G };y;zL<~H/-q"kR?m͜9[n98 Т#GEխ?K.z˦sVIɰBxi*ۮ RYn.R0HI(yAjK"j6l' L#]w4 A;~ͷ}]Y/m:PCE-"Ln*C 8CWT+x]@A,41OyE?̎f)Q`$0NSVUCkWWm~L__/k ?֑ k w&85` ; Tyeq:kM'ZgHu~BZjش[>66h0$3q9 *gKia6&+DL Q}B-Lc&I,FLÇfVa"ƱpHLzg]QhKZ 漝 .ITDjذe(% BeOan2x$6($*1;o5_L`ǵ=9v浿K?Gwh6=ѱc?(Ə.^d;ܜC/dˋ.ktu T e2Ln;^?ᯌ'a2D"fTHia}T2gE#?~F)*G}P4xDCg LH gT:f9h=s6x!2 ֆK)ŞބeM۽o{~]7,T9 .0^BRtq n-L"K2 )8qcto/EPCዬe1ߧ@LB&qٿu7lt"7=C.M,]|1&k[e8Қ\l+}_ǟ-Ez=LXO&~gk{ -;ݗEZ|5+WJATycm>kFUy&r$֓j E"Nr FP_Ճ*H+j}]`3*P+p&`|{7">F{UF# sDQvhG 'n|~}閛n|ۮ{ݵqN1tew4(H!Zb!T Z_Ux^$NHD*>]QfӉR$EkDJrOAVzk[~2ALZuvg"BbK| 0+FJ 1Y]ټ6E;E_>7˿g>B^KiV+'ԶO˧~~6B\f39u*$D$#;|dd+-:x//Z_KzMuZbikʲ|rӻ&[†_޽?/غEAݬ*] 3Sfc2(5`{}C#:Wb#(B y 7RV.t&-?xUrfʤLkIPJQ‡2j:+d١QN߬~!coNձUE D$PZ}If~,!xgrX+P,}A`I?{.vHi^" %Jia?\84#B\qťWO=חefE.\%^xsr#b"K޾1o֤W0TA xvI ä%l_㮏c<ʡAPtJEfIcdi̷ԦC[s>m[jM;SCGG>50+MS.T}h^_2:Q<k;c?{9:X B$Pet =S}~|O<% V'Z2B !jtn&g"l跁F]L>MxqBXim>+J!mwBg`dY]}ђE;}W]}ɥWoR_=2vD'|gƷjU0|z (,[riWFNZP A44EuЁs>v`pcTI9Νo]y A5Gi&giFJL5ʜ1ɐށNocpXfeKۿv+_׿WA kIr݊eۼ1MKK:ݔF['ۥwGKsEl>}j1Hz|;n_N`XBRFq1׮J \~Eĺ k~W~g H{w=P%.:/]( OD*Bs4|=ɓtwMR@uƪ U?)gim|t@s; \hD.8@HgO}_?D`k-ֺFm xu O%*#Epu-榓l)l^!r_z ryecE!hTMa|>wwfw$*T ))1l "?rs D(ghwƆe?霳~?_|Clwڙ-}!Ч Ϗ&5A cTlfb_J&|wsѹKW,)%W AC&K׿񵻞.iDB1cޞ%׸l'5V #fMf[l Ig J`؃4δs9Zje b Nq3sXd}b!KF'^}ˌ]u.>[^nWn#BH̖"y^`#P 7ꍑ—W-W~펻ǎuH,x@ G>vX7d -u>(k@jr?P:qfMb 9k +YhҨ "2R"%eHlh\y_*\{׿w αWTY2WdkJq1@wl]=oߑRXD@&qe !I nKW޴SZQ.M#1xم=4gcx ̮$0 J#=e:}&6֓>PtnNYochOzN뜳7~o?͗_uc._|9ZT)DPRB;Oث-Zr7K3hm7IAvݜ:7 |>υRp0qZ2l!`U2" !.+*1V6|2ja֑u0 Lg?ǟrz{Ģe6nHj>;#G._~r֝ƚ"RB"T]T eruu;59YbС#U׼*<W<9v_vW]ջ)FαV7Ji#Qh-a\:UʲV/ h:tnʆs\3Pjr g(q`"ʢ{6EO~,Z4p-סA ^Y$ձr"P0J"6lMq.ܸϷ㱨Hu?i5 w'AO?MOԒzdxd^D~v8L8: a9V *-peK.z/`j?Lk>B1%V\Uē $D vQC@0l8B DEfO,4i.xBs\Ns\6,#qR(@߼]NdI֚la֦G׺zC?G/=|&ǎ o})gY*Si)V3brܽBʾ7uMv]WTbJl$ 5j=vZ)ӀAU\a i`LgfE:Uu3cėz۽yr$6* A9}_˖/1 eKIc`SC8 "`֩&:%J<9S%XKm˟uxH(Ѭ&=7CyTűG~1tL&CO> NzXހA `WU{=p׃>Ħ Vޞ(jY/t򥡘|TȔ֨ ZaE T;#GG:z4iԗX_o -7y&vcdf6UM$Ĩ1HLelTnکJ,K,d,Fm%c#i$Yٳz׳{?7;fD?[n×䒺/;$W\,Ue R=s.H9Fy]71I(i=aBbvFd\숱 " +{'I,eԧ?ёC|~?OKF=rHZi>|ٴe˖+V\sͫiadQ'gUZYb1;NHaA}o>lL;_]?^zɢK޾{;֎W_uo[+'{&&}1)aJG&" Or̩d6mҕ+P>zdɪدv9Ÿ?k_sg?=DU ǟOo筂oc*B 2)81A!Ym$ ͊chLR:c#{po?39Ѯe=m@mlmblN8\͛|w?{푱#L#H! ULR` zF'˪KVeu2>}dzv~s_*[{ #bCYjG2C!LLvll'-wBkHӴ RE"bKdY221IB-yknDj6&Io}!2`Bq ݶћoeE{GE8+Elj"Rnj1ccc/sw,U,Z6 %U qLqҬƲ4IϞ{߻qј%ɦMLRIۤhkbY~۷;ٷla^(B MZCD.~lo2{)f@U6uźMXx:@VG;iT-:xaDP'; 7;Ӎ*+lgޚE*FR-!$bV(w&=?׷rWk c_oĻ-|=[=(ZS']j2)PKvN__~%&!Hv{xHfѺh$!|| m|ti/L̦-M9:yŝJL:;>v!9gꀀTN8v%!YG6#u?+?cɮv`mJ}>St"ȰlS ,]~|s]o#M)J. v.C3Rs|݊{?ѣ}Gkb|б|vVcRO݇?|s?ל*^ֳI")#EidCyG ٖ^K^K쒕.>{KJy'N瓹h.B)lo-S5i$Ӱ=cc;ncbذH٧yD FE88e}YaMjޕC7/Z>v .:2Y>kcG-kXc!:vռ4Hۻ;/:} :[iɽ˖q᫨2'4ey`Q ;uBG~ѱzwQ)B-'LzA7EukF{챲,;NeO=~}w]!-+T8Dxց!E1Rh "ڴgSĨPC,ZocG$22!6L@^;1,]l}(Ҙ~?3zwUGEKo|[]v%Hy ԎcM;yrV6W߸{_p>aG}'oذhѢ2/4|xP,;6 [fu<4>:|p| 5Ӹo<3ԃO_O?/;F,(3zW^K_uXZj`CֺٱV۷y!?н~CT XUz{{/^+ׯ]O۷'ǎרM۬,+׭xo+՚ X*_QIb%h鴅굁fOrel,H XWGێ9dU,UUc_.g}][cJ7YJgwm-CjpQxKZШ7ug_1,'F:J2Ocvx 51oЖxa-_G?x&6وUh5ɗ S6_r)|8t={>s֯^<4}c.>82R'V,gIt|Gd{.R\wZB+׮4z0lp;xPg:]꩓0iڒ2 @ @h";p,;"bbaYaYR璤SW#W=I6A |DL- gN`xTl(**cbfb'UkNBTA䣤>j% cQK CoM%&v !rFz/^HDBRi߸{aebjdWޑc#[|HM)!Z˵Z]|x[n5F&FAԞW*bI$y'u%Q|_3;>;EfVP4Ӥ $ O<}x0ME%f#{wUWRT"}!Yw,TTwn|6 565+ >IbQz^w}O_oij],%' Ag˳St3=Sl j)5vu/Lx S^TFOUKW7 V[^$XړcÖ/oڸLX[=4w]뉛(s; e+03z$> -:<8ُ}6/sde=W_ai8JwtUa %b2up~g>⾡]O8:6ޘp1ƮԸvk浯{wss+(.Dd\8aTٛqƙ%UJZgдX "2l$aac\E%zz-[%CE" o6֯׾rpH3$U,B%W%F__˛fK {l3 =vDHV'm+o uɦ;E dZtN=ByD)&*S=!CI񱨑)!H_㞷-u C'{R,&|##XP+}ޭuraQD$ hI/}1*"!I޾fOK.7{&$ndxf&Z3Yp/,WP#K<,:ɧFda7i ZϦ5.x޺{tXqN#e@p W~?`BJ8B j#C`{ӤVmuN+"PbDˣLPU⚫.C0fRMeP%aZqc\i1G%>+oԿf&=cIM"JрJa'vr]xek\sl[`T=<%vYgdoybl$}$ ̤QbP/xnf;%cknJq\F/!Vk~Q9n:*\fbU%e6mwhf4Ă@FK-[yʵW,jY{1ZBbK>$z+I R+gYx[Zϵzʭg^ܿnb<<>k=4M&k (jH .h孫_ٳOo_j2[hd1fJID|E%4Vi,#%=4<|N 4NVtZ|41fFcNX"N >x%&uDif- $`A$I@H?/>T#6"OjVJAh77^x6zN'7L %L`|eQfs* ,]@ RC 8[q ou~'&D TXHETH#Vh!a2gY]MTK(jDXdqѻxhiFLW_uժޕG'GٴeJ5*1f"6I۱/v%n1WCSӜSDd@"H|"@RB)4uW.^vsؿEKF(CTAcۜACA̤TuegN蔽cw=~ߝ[PFc'iZAS-]sޘ1zz.]zG68$Ԙ6!(iNMOo޵^q劳J_!ʲ >#Z!cb+;v!9břQe(*Z٧ %徯?O>82rh-}4\#llEĞF?0tn_suړ-_R@6{֔䍀Q2" sOeH:n9r[zv۔䔏̼8''%Gy,)zcOUr$$sID E8 Cl 2IL6ܞw] q-XLҬC.ekVٚ 2 Qj]I:O]#>(beCA}=ejyO -#cW,ZQZ `X!=[ѨvYm*`w7Zm=;#H'ZKjA .v{U_UH W]9~tgvE \7!s6Ɍi:ͤ,rk%?س۵[N\X"2 }koNz,pzepw9MD "[#$чw<ٟ `".9:Ā0)##QѷoI*tYoֲL3c6Tmo_8Bc(ri/o}䶧ݱh4Hw}ޥ{31A=9mq2i6Y~( (YҤ!%ۿջo(>ٽI֍sY6nO; -:n7Ϭ8g545 s]fwL;U2E9\4Wߙ~;/"^4o Ix" ka5DQ0cJKrLlDD#6zۣ&c`ּ]yk-_ Yi)LBe)AT(Ct:EQV ʪwҴ6Q .[ ^&0xKf7՛Z\r}#tft%e޽;!4ˇ~⎻~ŵ4V/:oWoא@ %X|O?#QenPxj_5k\UV(UbPΗ>uϟ@bn` 12N,ߴlymZtlrdҷXz8Nx4ơL`KǙѴ/}-wL{;XP! 떭ܸbꁡVX3:2>gȗF 6*6@l2m42g)bʵo|tkueYꍺ$1FOn{ugՆx]`k 6}nϞ݇|]-6%˚)[v&;c-ٰq}P_k?s=ץɢ z ""W"WB7AβIOd ` h>N&v*j4+TEHBl1!}LԤ)+ 9GEўOmԓ>[wd{c'ҍgK 0Ī3t mwF`Y i㹅K]&+Tb J d.]v\s՗CeQDN,BDBTH y/&s*Y8::vz h" ۇniR FP|yW{՚?}| &Cv4ӏ@cO}rzܗTD .:7^F@4UN3tKpx/= pRx^a?vRCDB`ht5K/x5k],/>;[˗.*}'xkc$*Uw"6h1FD$h\1BbL GT4TTU% a9 &2bDl f@-&("J\~;ht}ÇO,^-JYGXOk+|?0!ŭ'ѫ/;99߿|4,2x@aH ̭Bҙp֩ jFzͪ]'11FAƐHUlX6%3a2A}Q HDJTrQYl5ȆgLEP*jDըtchSױazI b`P*(k޾ ۿrSfYVT4h͚ .8^׼tIF$ nI jEcҚlu:CDL]}f>P45)eRnygl#g\cXfCl['UbaQu'sZ/H;dc=CCCCG )v[zydxDROj1$ɋW]j6i&R(\&DDb!EYK De M&OL};O) %o$Iow-_쌏#%"rWյ+'/~xgBOLQ"#bF#{oH{<'P$A D ]w֒o:{݆ԺW%VԲdѓ)yP1ESTtض2@Ѳ[Һ[Xvҳ6oW B,GGGnݺ3!Cb+jGD9&i{t| &PL:њhK/ѦAB(TjY?C> @:ﲾLiTE8a| D )\">;I`G$u`qI!H@o \bB}xs5bBuMӁSxv{&a$D"6!(T$K*.2Tɘ C !hj{ngoW$uKV1˖,Zr1l7?{u@b$4 [+T5(d+ϋH`f6Ɛ*lMs.Y*0H8b {)bX0TNM!bc>w>![Cص#|(;RVlGCN5ӷbHfyW~V)nDi e UdU 8xvw?'Ӥ*pR5fjZoOϻhd1~dl?,JdH]ˇn%?#H$2Dpښ>+#[+:mwRmr㭯y_ kSf=xj&ԝ1&m$j+ե>Q8VđcG6ё'6eYE1<|hllT!F i߁GFy}{A8Izؙ ֟;{w`"(lb*8mM1PbwΐR|)ѬکN13f#Jl'>ECl6z>xPoo@;ݳgŗ\<:zguEQ jޞ=Ƶ[&79&`zl4$Qf`El֪/%FkT^!цeԙ΃C.;3şj9ZQ1FN3shVfҮ%ѭTRTo`4I;?':&&*߿w;{(D4h ٨ * BcX1.EL>2DQ,!f2^vo_ݗ )D" ƐHe av Sу*IldChdfe:Ne@I-kNLLLLCȫ}m<uWN׽g8s1OE^XiN:`}K1$֮&(USc){gv526l=#j}6Xk\z}NVF7s,|I=:QouJ{ي j~w9D}lIUKW_sçPo6 -Y K.7_r'5{x>QS %X:N&L/a_*():Yƴg*`&"(ZbLj x >ёFYēKОΉɉ_fOg>>bCe7oe?eY__5y%K֮YcǎёMٸaO>166rgvޝkKZbXK }\*䝤ބa!APD%:$4`dUAXe 9NS:/hls+H&U R0QBD92,S2ƺl8MX!i $XO2DH!soe.ҍ7qޖȵʁ \&%*|+/S"e#{v}'؆ ,&PN.\2^L#m{ (҇F Iepp %{:xٝ ກBZ"۩KJybR&;{A;XVTR,[YcqαIr|؉O\_'rdYVթF D/2zs?`")=kBBT}s_~dv*`:BܻbQߚu7ܛSb@B_;6ګHHI,<顬7թcg6oOˆKI0Y  .8oFK;6?~~dou|ScI+ʪ56>y|Sȁ-fsF &I5ݨBQ A7޶ǧ*Um%hy,ΉsIE<Ŋ&~\i RJ=\*u=3?T>W\~gݳw;jկ~-LNŧN~W83t~R ,sHHUzh\m&͗~ړ'OZk 7\"MszOOumٲqժU~>jfօB<< I,r9ӬKɝh%g8V ZF#,pN ˫דLmi೹qΑy9`9gDk3$-8xt'Ҙ^$f:_vV\~e+mغYfC(grt|Q%Ii8͌bcZk eL8h]lqmlG_I"~'&Zd$U`y]El8"2XAGfffeqtFX)RX@-;ou]tGg>'_pIW*v"Z$=3>˯;IXZc zvnqC"4 d-sG_76Ņ Ycf4:9>t#cCKr(RX\޳4k6׆uwIi[f\clUʡ*+k1rQ.~ۖǎǶ^oUO{k2!<@|ökVz0CN<xfAHF{>an[ Αk\F|k?XB)>"V(g %2]?70M͊VSB2c' r;yˇNAJHK(y_'-/tu@yzd\q6 PJ*z*:nm:өVISl{BH0Ee( T7/֦=duvtŤ2'1ZX2d:s]Brna;>m}rQ{]w#9oE^MgqġN%Z# (j+muk uRF>om@X@GsIfj4(PqJ8 kZouS;n-ϴL7j=A(e7[-KmmRș΅Վq&'%ESj^LgPxJq@ MW-lRqD.G)9NL6Tnj/MӴ^5( ?2Z&&V:;7l(@h~pn.9IF!2`DHL2c?$D\`Z} _u .c. <(N@elL{[=gJ02)z\?87=jG8C9cHp^k e4dmDd4Fq:-{gz9vC?`q9=U^\ ;{MYG HΕu-"z2tksCCFkK#\<ӝK  q)?wC$Ȑ1nfn%+3mK:BŜ&֚ahnҀHL29eS Ξ %=ApѓL=3hŋTR@w-kIi$ FzOBF78/D:[~+WsL&jD,HH-Rooᏸ,0Z'֢m}=c" U3/.og|5js1ɘW裻ۊ{;||0%T =/Thgsٌ8NQѬWl&g Z9 8p!dC7usZ̗L:1$ CfyNp9>9niXə鉱Kd-%€$"bTf "Cd# pYL &*kHhq-`=CeL ps9#Zä̑FL QhZ /xGWqۍzhWoO[Աcm=ޙٙ$I<pCG{@$dynҹb8we :&ͱhGO-Y]V#~—ZTB@ Tɼԟ97ï4d \E⣗eQgVH/:m,/ l/:JDMd"^gKW<2AL R1~1WHZ)u1_p DNIڹk>20 *yvfCRDRoxas&DB 8-!9 D]-|B<-_/c,Th_oUR69ϋN863Zp[DȐs&ͺ<\(dZku3Y"JD$EJJ!5: "kDlʦ8ch$N3`HL7)\zQ$GhDq&6ꖈ$䄐֚Ffd'Njnu[Ϝ>{>O э1vOF(MbΘ(JE?见0Ν@5kCl77AZtV0:7~s$箮*Hj{W~SJ1ϳxsj%`%:Q`oy{fd@˰7O=x܋kpp֫6fϴwe [Yp0Җ J B Z`"&ZH05@9B2Qx6+vx1kfR f]]L\ҰC!u 2.f4NNxѶi*-jXC-X-@OZ?^ښdKtj"HotsŶ\7<_2HDvݵoIlʏ‡ǃ}Ͼ?>\4&@bɊEa[u֪Xb 2rugXm 8d4YEm#S_ӯ?ggfg G 2%lEKW/^咮ΥdSDPle i74FSFpqÆh{O fl6*n64G(3#GAƭ5qeUUC ]zQJHhChwH"]sr3Sfȑ0M\6T‘`#/X2>6n]tww~Doo﫯9#߫TE&ӻW_{׬Y~u͛u/Y<{]vܸ}q;ΛgWofvV?; ≓wbbrQKW8pp25<QkBXh14hZ젩Sc}> ֏S6<2wy'йϗ@ YS׾º *3o*0΀1"DK$%{9#9\Ԣa=|g^dWiV{ur}v(,(,8iH9= "H)- @@Z,">7rIۯ~~\[Gۢ5U3B+4E]0$7/4; ]T&J1>[ex@f(8 (ס@i{X^m4(W)hB[ GN}gXZ;9w}s5m^!¦MSQT%8 9Ϟz7̳csd2GIm}O<=?KϓAe ױ}g-KuqXh{OLMs{4}߲:}!Bha߿^^^Fx0vO?΢Z2)X~ϋ'gZFya&Z5LS0Za~Ŧ8M^=ݷSN;n2'?MxՍl[q?y+]B1JMnk[lͩ߾C 岱o~mi~ +[ Vs:>/?Q*( m*-iOdͅR̩wGGFoidž[?__}6 OLLU5#q@m0@ȝs&֖!z2 2jy}GO<_*ƆfO;}ؼeMA:l4Yce[G w(Jm^oT^t@ 3S+0fA J>4xԩBx}GXJ(lKj eKOzܾ͎̄suD.O+SU?v.~Pj١7vwu= oL>753ձ 889-/RqS>2Nq@aM3DB#Ș^8+B xϵOH_v|/'LH"bBRre4NIO_=;tБC߰}mvޠ&"4xߑ$D:Kqj ![cD|CnT"Ι"^ 1 bB@eOqNq~U+žCQ2&Zcmwg>&'?W#燕M ڧPÖhڥ&CgΞJS>ё_FFعo`(R*CdF3$<0 IhgYvATbndi62ȹ^ HQZM?/?!2 GmK 3᪵6n[~ )cRd\xkB\@6m6?Zs:d/ޕ(.'`cwuwWŗFndPZF%N*739k @Jr:Jލ[օ\XM۸e}g424w9;uęJ13_\CC3գ_3ԅ՚͔4\y-;nSq IjR?6^b}3],sLj+y&/?+C'&&,{Rp ȰݻVƌ/5)gU8s cgZ5`1`>zyvtdJ^m=|3tGϭ[s/'St3NmZ$V@_=wݹdYқvv|-[⟝?{;}x\#?vIJ+֭_jKWYjMLfxx^[=;.|%iDKXK] 1%<+ĐcHz?;ufH Xg?N!~wzؾƞ߼n)$7F $m 1( yViG>M"R'=Kf U |?UsA8sgM% [~lI0jFЅ(1Z%i3mﺧEO!WXҿdO=5;=Ө֭2''_?19=11L Zo("6似svV00ZNjo%jyvxϻq-}.mj3x̀4o1k-eLQ4?AK?N/~VZ'm1IV "p[LhR6ykH0[>^NMؠrۇhu]|Iؕ7B_>c T@d9ɼ'{[|}{r3g}bZ[(wN4*3c`l>e@0& 9"1DFĀ/ش麯}屿ffaBhYsůjdvV& )9>]fր֠aR ~FC{ٱfڨSLZ J]׬[뒲eww 9GZy+u->s kP@J\)WnO,9ga ue"r`@4['MKipť;Ze[Ϝ9cS};cǏ?{ّQ1ܳ?G ,}rpdtx_s6Smڴ'NeKJ= 9C@"guDLs~̆-`0Il<~fppt}! \}z-#̇beN*d jڔt*LRʸ0JiG:"b1buȸ5r4[Q\ څ: S7VBA` h u/K)R%曷p``K:wHG/?xĉr'IYKTj@3c,:~V"_}{259q,nqs]F!ss^YV# ) .LH K qD,tyr}!GH2a(-]>f^wI3f5U&eQ!ܸeÝ޻a΂!tz=Q۔Nb>Su{V͸}'ϓ*1JٙXerqCcJ4ق_̑s32>oZp[@xCs2AT;͌O?O 0 ztV*6=|$F yg2a:pĠ-Ho*']oIM9>r.[hooo$OzZJ1Ͻ%ƙ/P>٣wH\C%{{&9btiҁ .a˲-=w ˧FFy('6oڴnoG 3QWW_{y~ -2<<<<:l 7Z>Uk4-:[*@!pD}h8)}uQt1`\f[?*ruvI?`k) ^ L#LEcSCc#k(j3s+kdȐ%q!8|P@u{w}GGOu+Zg.<ӷx7cp0::yIG Qpz+C(kLr!@\{SLr/+목R$B)6"ԄZL)EV+nDg԰u~} *Ap8A;oЏݧ5b*%Yi&UkoDg::N>79 $dB8"8k&d uZbD.]>*J3եk/_էFB:c^\:,6\ӇϔXCCr=`Q1\}wj;eUI FWOT/[bݻTߕc*h 1d;s}kw !` +49w0JJܜ)y,qGQR|ӄ-gE١']a;[4N1[Wa]ЛHH2~Dߏ+{Ξ{(ټeӯ?eX81[ _2D5df3 01ChJRy-oo IYPF^IJK:e8IW6x%hNA=0/ȅ";31ݯ~PL;G,Zb}[޲esG6Oe}R=kR"m9XD3.[k 5OcRK/޾yQT9JzBs)n{k952#cMW̖NUh/F/N ?г?eW_9Xk[vܺgKgϽ}ز'~[n\aUz<݉ k+"kʕrؾ @ +7G $hww (dѣR3ޮ{n4G}#Sccc/ /4,9\S'mŞJgy[g>QU JܐoFyXrq֢, thٓ]&wp-, ȶ"2 J/5" (F <h4ʍF6i2ΐd dm+ Q;sc' OO6vO[BF(ID4ibGOwu_N_l)U=D>/&1ERaKMY YQ?[9x3& 14D+D߹m[2ņF 3LA&%pmUbs:5`]jȹ1E0ٸҵ]E98,XTcDB8_4b5QOWѽ>\lXwX^{zCU=UzLC_Nώ*l{^!'=?4 3_v?Ʃd-yyx8t u7nVlY5S2cɢC#0J8 8n)8gZ74: _@G8%+J^|]]q(ىXghlrC]yG5M 3"#HڴqukzJSLMK}|)Z7u:Kbp+Oٌte*lo+ݽOMLLO͎ M;V)(ڎLb{'O'Ѳif,'[H:1 +˕ʩӃzVrYga菟;ʋ/ŕrUwU/{[ONOu/޾`Qw[Gd%8cQq]0׭Y@ց`7~3006>>2<*~L?+Ix¶/}+@DAitz"a[chbz-;r=]=+RSM䀱Jd]#,\7Xmf501tE ׸u {ą',y{Ahvm)Rd02B8R}|&z:XάDžbPmL6S(F !Tҕ-XV#"bZΌVYǎ,*JBdsh1 &IZg1$򌁬0,ªvp+4B-OiyR橧vv8RN?wF{;?OY?~ϗZ}CoGN\g h˄gt.rfvd%kjBD)/X fZq@"]ԀI{ vc{a:Nƙqֲ$z4Z!5V#+é_ 9c9h5nVfR ,Z(5 mu9x ׬^Wşsº 5Cǀ80ցrҀ;xlx|꩓#Cg'JY'2H3][8Fd1?ܬz?$!e=!w~dԹA)+3XCDDdemOU*<~'JO qVg“~fgaK=)|0>5ܿLWiUH/e'j͆o?ILƙ</,eduN w El:V6ڨf ?3]޶}A.ukgfg\_D4J CZѥgK!^§iY0^{r"B64;ugs ~/>=nZLkJ޼~-1:<ݙ窳Ӗ!S(Ky,lZE!c\HVISU9vh^G*H ؟_M[_`\2V\Y(qT D8x1 $Emy {u1uRw+G]=G >x=?:%=o='WW]{|pXGoko^F<q`4XLj9\ &}60/~'DG:&|GOqsa@ sD eiճV/`HGDXK!iH.& #Dl&Zpy&dGlѺ&NKB pv.]e 8c4>wԩMB%iy-7:KcX _!<Ϙ_ 8ƀA[o.rhl&k%}-3%3dƵ[cCg[z{7ܺ)!;EJdTDRAƒ4go|ñWNκd ggl2 =CW^wFmb8::|UF'FeRj:r@ <]@b n k*pI@،5kى}凝Q׿'OJ 6A@}=ٙ=KsY \-wE V6ti:Ut9 cX|i˔Q&iW&=Lε|޽K ƹsԨPq,c9Ɛl<9W ?\i/}8mܸCH=F-]R9b AQx-E"?;׿:9@ p`iwv58kHژ󰫭 dHDeP0pit 7uPFo(%?:s!P'V|Lr4I 2& gS"`2 Luş}|PO+GJO+H.C@ Sxl~ՙl,;{J20i^ tq@ "B ԓWr`˱ vttnyo/.n:cZ%})<]!.pktN"}E;]FNJ3yMQ-y81ZQU#O;zs2n p[,]ng:=~宬((2.%FA@ 3KퟮYr}MԪLEѺ5+WZ>u(o֕+WRܥ PR:q?u%zR)0eFY&XK߄ ȁF0¥$@2VHe:V VkBtyKL\޷ޝ$X͞8t*O:/-_dŊ|}KsԿQO9_a?y}#)]ÇE/wa߈ȓ9пbdFQ6MmGO_Gx7YRwg&KG"B(_W< \>zC8\8p4Z882tҐB :9"BYѵzÊѡRxAXK8=.%`vf3ut/G,_]&~ל͋WsPsa{Μr%mBeT&%@pD^~iq51>(kU6Y}H"RJr9!<2hp)UKU3k(H.BK2H=ŴY,g"/8G .ǵ]9'ѼGkTowǽMѡyZD!rscm&'f˳ \p`Do ͹e]REHH`-$#rd[8[Ige=r2& ^;@0z# ħύw eK3Kӣ~9u&5'G?O<uvv;qBBB`ZJJiƂ3_x?ވ#C<$ŌYK_M\̽$g*ri\Öo6Xp4Z 4 ܂i&ԪR15 c߱^YZ*vdJ׿KW=s~ddr6jX9bzm0oݏ_ 3 ebHH }gzidxZb:c\t/]thv$.R/=eTEDsY놲b. ZkRWt ` `qcyBǦF?]wS/^)ֆ4r`I4ι0ζ(\ ޵g#_:K^V"8CbՔ\&65[Ksف2rH2$q~Ȣr{TLs`|Î?qC/xm噮 ۴Ur̵$o` ?BqNRqkJ6͊E?f&g$4|’ޛ6!z!vi@P 2 AK- ZLѣ33tii9[oνo͹~P(!ny)K0 ǖ 3eB& }A7 `At-U/ r6퀖XKvGϟf6q/=^33gO/,0H퓏?}1:Z3^79bE%7P a@ĺ s3Z@c\C%c I@ )_:yM˗<{Xl≧۷K(YpC=G_=:!IZ`;sBg4 %/TJ!Svtu!AfGma?21z$K:DŽ`ZZ/<=sd-sx.-kUr\0j.&MSBMR 9YR\~/868ޏf NMX-9 |3ݰ[: W4.8B*%Xc !&iX@ 2T牡͸44#G'ts19=zݪ{};Ƨj|bbdkQ#Z+Rji^,4&R]C/}{cϩz5Vm߰Tn'l`bm^,s- o{Ţ4Ot -2YҞz#2338.Ŷbgg‘s'f2>1+ajm.99j9ȹ8 n{p0pm(Iq.7 Xav`Xv|Bq.]T٨@uKz̩gYo6 !FGG=۹b Ҵ3\讠8[ʑqsm!C6qet '%GDg- 0djdKy /  ۺN/ RhT:e!'d ,-+-;馾P&g12*kSd6ᩚ;]Iܩ/?~w~s;=yt%I @LSƧpɘ3`z7nZ:Jjl ϧ:MӴhd5FJp!ʋJ dW_ЛJGijbғLvI3LJ=|+w~uޘjSs`CDA2e#BZ/^ѡt:R0#ھ?zBHRt.Y7Uz-#/ OMN>Gf:m/LUjH f"!㙠(t ZE=Q2Us^R+Us駩DF[J\FݔB:o[iYCI:cH ]ݻn691qTǚK:Z1 fRw =ϷȅgԹ™[7 |,BbZ:;[2]=a{駟ޛ:-XQK 35jzՠHRXƍĦa/:s\"iI17c -20NH8l Yd 3A#`1׀<,':qC?⧻tHs9q`:A=^~8[gEr9|ɕnņз2$`H =.B$ֹ9 Gd I@xNgKS\ssC OUxKګ7=xq)*Y @p[muFx|wwrj67Qk͆R)Bl7l]Ƕ @@ 2&F'O$b9!0nck5|oó,D~1.֜ kmxӝgk30r6Wٗ(wWv^*-1j8Z(X "G 1LO.T,|^o09blƍ[_Gműs9F[j?sMj=g-900_7&$YdIWO3CB\>"T"B&g9 }ܕ`3Džu  uزD֙VuJ5}1ȸ4v٥gO>tjnА 8 =YY7y8x|]7n_ RE>ӕrSB{H"X|О`^Cӳ3\ 9BHgsGf

}'gҦmxjۍϟyݿн·㶤\އn۷U+V,YR^޸fŲ/~N˵|tw.Z4?3{@&D.kT`L$x{W䄲qZZ=0Z3Y'CCښ~Yx\ٽrl\+! 'uV R0 b 3v19m~rd sDJEƃUUZD"VZNC_=>uDk0 $xy" 1e(>=5U,yߒuI2Рh}뷟71,ƱɩZݾleo-2&3Gs;J5BJ֪q.*᱇Mv ,YwoڴNI>ʄܯ7k캉Į&snYK/붬^q%`,sؽ{۶z@N*1Ge2G}|=cO06oڒ*=8rʵf.R\V*eQl?{xPHiH]<]JzqpNh[hG$83*"g4qU˻{:$u3/B#c19*W98qxLjc]/S!8"a$13R`dxxdd$"uM9#Є ͭ! qc#os CbAPjtz2䳖L[h ܹR 8cU &ra_ u3566⃺o=69;i GONOa@ mظCbɾE~`1yF'i3|4gsЋvR 9.8S@`)B[ e(_]z..#)8{z3Fe5D?!/5u?O`QZ"f>c,:Yg2燆\fCLO'q ER)J)n|+*Bk5 FGF$iV>m&en^!^hL~98ݾCGxrٜJ]J"MGyVVp'9 ɡD=Ox+mw|QpqLuR4 (ʪDK!C%0jb5Cp/߾}ƞR>!lZ+@k<R= l ]?  gazn!'G@J%nƅ6*Q[3JӍ#!E\xKz{W_~˖/ LJq+zt=Wiܦ.4F;0r9mLA02= gb|bb|bb]}SS{KJU$ߖeClgKWr B+4-q'!bоjYq֒g{O>n|)#5}|~ɳ ! x-g8L}-5P 7FRsic峃#^+zŭ[VDdH" c"H.H$5Vig̜|3Mx`HB.[i*7KIUwF11)s,)m2&/9k|ZQĮn)$9B CXPJ9g;glcgi,iAR?ۯ&K>kѶ&&p0[?5yjv)ӌlyfx8 pWq̋f0yw<|x3Z 8mX1mIIU˱qX=w1)0~>qLxͷܰ=mG~]j :wZ|\ӽ k BfHyc0C h zW.D =wॗ (O>8v)g Y +Wk[rl[wqٌEY6?d5 g8GMQJqAZ9)%c"MfTKOz'Ƒk +OdrzOD {{ Uibl&]3gqs9kBr!she,0!Z9g-xbI<䂑rM`5ãS3X_Rj-:<~ԡlT iܪ/~BaoؖZKrfuzrtTMR}Xʔ F@NP"OVfA+0g4+3Qe֨: %Q& #_mb.kz!tǴv6u1>|~hj|"SBhc[ .6IWW\0)c!_h@-qmSd< :\TvC˯e1^U/E/Ɵ8!Z9OFw_/~Ѡc-J|trD 12 iuȤp]7cc9'|vOku8Yi-JyErϑYCfVjܐ}ZYZJ%O2#0;"..4rr!\;ĄPWMF U \mk @.C H9GGszims ki :|Ix_S 26GΤ3ȑke1e)V/f?ɏ%t|z&e?[_|'wKT+ g251Y$2IBZ/|ΝekGk\#/6I4r7fU3Õtzl$Fa@ePpJtv}.MHgDzSp4~OZ;]5]ܶղ-PG7,QZ>lAz觤_=Ī55e.d:t/o6[*̂lGO߼.69!-sH)хf\0^|-72CO0\2gA@?RÜռ9ר'N22AG (ۿdYOo? DtY_G^"%\En4Cb*gN #h8,+C (ZmBoS`!><3;I &!H#96M=雦>}!c{"2ٴnmԥ6{"Μ#-:3LX9lOw+?c yBDWM·^/j_at]+ 0B=UffhdlxT%gdhԩS=ݽLL7i3ftL:EynUK}a$8F&~o{֍z;;G*g$ghQ EY!sb\I2u ڽ/SQ #YN,ʹ82)ȑ9E|4" %kay ]`گhF5?uBRp~пh1s FoyG}8I% DKօ@FJ;2ù(*wx󱃯3ه/O:Ōcl6ٙJ%urְ (9eY1Q}GO~Pa\ikV_V- # Zyt1/iVm8R[u+#gNs GgfNO-pgY f>d@Cҩ1X.8c,ڂ @|72}!$I-3P.8"Ih4ߗR@ǔL7qѩc=݋-2MOM[ /xu8 !0l%?|q.ZBdG~i & ι%,1drv:r owhJ saGdĹv@ HCzgO ܿ;KRʕ'koULx@(WŒ*]\fŒ~2vzrVhhO4| K+KNKF\lN6/&3soժUH1VTù W^}wn֓Vvˆ`nЙ];o_jE[xqZIT;<:e^25ݾ D A:`SGQ䗙FW.Up^tHH V9d۴i`C&8!d/1ə"ӧOQ[- JhjtuFA븓sf {mgӺjEY], ,.z={?a'&rH0PZg20VsOe'^=}庛f 6 B":xQ])>!X Ir6U"qL"}~c4=3U%eIIFo߱cK^z{|f\s6*z;F338r)ui ɀYcѺ٬24JIiC{~&C/nftvv~^= M7GG/ZԗemY'..*\SZҵy ..N8}kKIJN%#DC6 T2($d Iu}Q^m|~x0rype`a>ӓ噡!/ ܹ=/?;O8_ȗ: SO}όz(tP)u˗fT(HZ 'K?0@DVfY-֗n"l.N[0#Nk@-xy|fLQ(TYc['9V_IG#21T- "ЫGOoy`.Yf7xfܺmVXgϯېtVX¤Zg 9p31 " W\ZDdWXNѲ oHpxكkmRpqg-YTh+$ x&ʄaT*^)&Z Urje'*S!`ÊnG&-[z~/;K{e6<7>44=B@Lʆj*\0Aܸ촞:uҮYg+Z^X+luVa=h_8cYDζ:0u.r9;x/Yb`M 1BY8zCgfztRh̖Q&Z4Iu%rdJ Z. PT*J__yiuQF!=9ƙ2ד4!Xn5/ZՅHZ.X0C{9WsnNP nv36-R")ɴH%[ޓ7-~,I%R6ES)6jfg4r,*{nN' sު[@B zwskP`?={NT>W 2 OQ2 Lu#"+i7(}>dk/]7 ZFKrZY R8G:g?;9k}}S\O81dBJNm࡬ۮUD. $͘}\4 @@:kX^϶ۙ c KUJ&BSj;3u$M, u`7ŋsǏϲ;OۏScCsf7#l`#}}<{,%ZS&3|!{~r.̧[׼S|l]ATr;$ȃDRHUZ1@BzgRi&'>T̿i!K Ȁ 2e;1^Ru0{;/ܥyg@BM ܾs(k5bGj{&GFc9\]DCRGKH:Vvmp}maj|Rt%EFh;}#g^?Ѫ7/,=OGoħe321rq_\ 'Fe̾وRBȋ_=DN/}W|ive3 Ǿ_Wc$ͪZPpv="rZ)Wb8/GqTRvJ,a ü"MV!yxWFdž ;U¨*% 0&E$!, EW  dP=1Q_U=`f`h\s;_kۿ]CA8򎽒.!`=̒'FK3O={ YƁbe5ӎ"kxldX+$=WBbJ9~^sD Hc`sOWB?S?~;A2{ͻaX((u%UJY߳f3׎;}洱P*|h;wrk' /M$3t:`Zj}yi @!')Xo|ZٷT:_ /]{rqȟ'r앉L2gJ @EFX2金]]}%C@iB 2#|n$,$PXcQx(}c. q;40ёZkHTݾm@ ڝ(brv͵ν7Ł0Tdϡ/|ų"6]Փ4Cؑ%BczY OSm9v8j%O,cC6 AyBs*:$w~8U?j _ 4Ϝ /1mWqAeiRJJ~fnwwBAe*IյZvyQt:]ʚ' qi-ƦgBRP2Σ`X0 P7T\ @قqƄ׻Z4PJJ O}QX>[. u[[zEq޳JYk7[/?_ jQ~ܹN]4X3GL8;W%%:ЇUBFB\)UJB)Ao&VUaDvi~\|ԙ3^Z@W"W 2y/ǜLxhp([r0 ZPcct[G位*Zm~_+Kʥj J9juzd5)XkʊlK/@?}%=z|ȺL_fwv7{oziT:w׮s|n]QQpņ b+4;-]) ;ΞMx|tnc%A&wUF$%:o>O$@%ڱ[Tr&>h4Mt9,VK*bp(Z5'D"Ã3?3[o;|S\ACY ")rzuV^W''' 9v_3LVZP4ZI7} 7J 7dRؚF. QPN=PGe~mu'YD-\_,‚,WsFb8Z*EJW[Z]mw;;J@sT/{(v{-fQ{$߿Gk}L&KRH~GsHl\=KA>|A!_}7& 'v \NRdY]spGod )f}n#9RtI#rë/ld:dxđn^Fa! VDx@KQ\(֥p)|>PQ@J` 'pq6Η^~_|| oﯬ,?yTX[t868"kR.^~(%b3``vޒٳg߾hDTJK/ݺgݩъBijw.^ά8@fw<4OwNg^c ֿ'M[Ap?g3g,-;s1ԡFٹ$ *|>>n+K$EZnzKif "q~W\i @#h *%dl#"OL+$ɤnF}if8sVWzY3Ziu]$gP/M31?ZnۮmRg(8]f uaM 7_|U|R/ J I*b5(݅BZ~FB$lS3l/}SD\Y7G)nU֖K^oەr;Mq*ZYJurV*UMN+cCB)r>M{@A*!"}|C@ z >}ّ;A!Ȭp 2np8@`BT0B*t,"b{㋹^?-"ȶO z;wTu 5##01!822R.W?23}ڽkϷ(T?epWBzmH¨n77?74W*R;NvR ɹ놇N_\^pBZ <STtH$f$.E3\iaZ'@1 ǹ!DfNOߗ|GhMۣBnyqttQ3nAnƣ>x ”}e2о~o\UO\j 2/uw(+¸!I sLqyyZjZ\\pWz,R%v[ƀBB> !ШwӇ!F׍|[ͭ(W 0Ȝw6eOZJ9͚{, yu=q_]>̺PВ<#Qr9?mS@nEaT8KLJUpN(cP4A >uLTBW^yo==9= 3W{(PO~o[xw+Irt <3ͥi:4iK[%"3OOOz'u@ HHKM3WZzFDKRU,; +ʥ*kmEwqr=rccc㓓SSSO?sGŋc^ܹsZJs Tqr@ /~/-#w}v]+zuYwя~O!dP|i= 4̦M>6Z*&Oq)!RX#bh}JIPeH̼ O)5UBkD9WB(˲rz}aT/;{zy7f PIޯ-gA~iF{edxD(0:`nt)~X;(F|+VXgzWSVVƓF#xa{ɬݫx_B0<<Ɉqyq{Kok[z+3ft661pyC60JEĄR c=*_ !13VJfMMUF'H[ ÿn01NRBk8fU#)Vs;wygOF^|fsKVg$;wv||R^IٶmG/~?~I(h6VV/͛Gsq[N"*B@N`|jhhD'$ {!!9@ !JR(MSD 3Rd0MLZmxd푧~Rnm&Ips>#>"m%%#Ãkk녀iX*}õAzV9{\ :uP,Dq|I( XO[3~[OvdPJ+!5ʴfIۮ};>ȋֲ0 Bɓ'.8KƨV/--t[`'OHi:έK?m8zgc83/66)!N "({dBHQ@QX8`CCA^!ֺkm2391yСǾtb[*wE595J[gl Ð1qu{]dZY{l&lSP26>56n?|(_3-!kFyzhԛR/^}5̅(%Y3 Zj!R{()PAiLIzU2KbֲEݹp|^R f2}.|EXރB J@fvhw08X][k(ij*\Z Xx,jiח&9cBTLLjJ!=܅o?37?}|9^|{tLxcaL"{DXu\"B оrsRwff~e}z;wI[_핓?'.!{*(z̩ B#" mH8q;=Svpylphl'o}cdϙ3 A"ғ#pH(Lm?O>灻Z$ n!*Q?D !lr3ooYC! @~cw׆o_+/..,RTݺiKC2}q׎ܵ{[]8?IH S>)kAȹܦ7+CTdQpS,$%C y}ys?gΜW~^p:& /@"DUcKޒ{Fcňz" p^$JIJ)!3/۷+/Q, P8.  㯒w\.E\[c#DXGtza$eJdm}/k5nPF | 7niff rAH)sGrpsJɕNޟQAP(yOWVeąRephlNˀA*/*r.$I8kZַ7{wG&džO=kR;@A˔+IDw'%N` ,V*O?mMqH)Z VӧVSv, #ceG}Zcw`{op[ʃ%յ *vS Uxk xlIr9 ʯ_p vFtb& %#?bC}Ab j!l`) MB'>c{g~&[Ehk *x&8MPz^*3 fYvĉ5iYET[{JN{c^Ͳ̃"htdT+uo$CA_ZBܵg,eFIe]nK YD*E6zXߏ:!p|bl;T,@i! Wk^:aҥ^;D+"IloeQ`Tҥ=$yC^j9d!Ǻ1NMi_;vFeEFԯZcGi;%7b•O|G}ɗ#}8Z~C{}3*WnLôj,ò<|]Cձm4=䳤4;5fR}0?bMi ųggT)GaXhLm ]Ff@9‘Z ~ҿՍޚNƅ";j/^7kn6}x3902@u]И+E?g*Η$n+?^};Iͱ yFWVe_2D}XaPx'?v>\_߽gZKMd$N\*@A*#G| v),,.8qniPe& |fJ '&*JgÞ̄JAlZ_*q}|lZ}^-3Y뉽c 'gMޘ60(<}|n缳kʣcI\5gwH|T}^0K . rՕj `ؔbu6UI @V%DX r@u<!tm1Ɂ=㓯9Mpy%vh`[jkǎ=c[W[nQ48{ޛv.y5uw]2M hKk2-I "9`G?`%*z깗N6I׿y۽W? +A(Mj2Jd8rq4<\ni]q@08B`"ϡ7w|\󍢀wŖFο`ַ &F*7l\FxUpOJ#@@dMi&<J[O}F0T˵$eX*Vo?3_mc?:bX*Ql6v^ۻgVٷoٟXv.4 Pdۦkx. 7WCo$6 TDX) ܹsRSYf6`*0Jm߱[oJ[)zbbi(o u|{5KDO$@گ% !$2aO^B{w綧 R !\g T2o1'Z[kZ@Ato~nv3{g?!nn-rם xs ǰ[6 <YVEV(zWK^7[r{ l_y-Fy( %2F\8~^inW,dz/40i(弗mZ2 I}l5'n~Nz򂐹ę^+>p=Ua|l*3]|dӑOUjK8 yVBm KF^ҕcuk0e%@K^? `q_KOjkT6T(ʊ A?;eY_zcpXXz"'r)s&M@,d7$g2_[ҢւBXRw5r.MU%0 p :KhQpmR95g Ҏ}v5ڭg_}񵅳ԟ>B {v}{ZЭ}mF'kR  @Ŗw蓾U!Zfd*Vse~xŋVW_?zl`b:SF&M1FZ12R-E]>阽 8JKKAȇJ10B6k*GPMᶩCRilۋK/1 !I4ՠ QVJo~HeLr(F'!*pKB,{.]áO?O_`w|щggM ZZٽcw(VWj6Њ`] Yj%3cd=i!aZ)[&h !!wW?A04^aBId.$%1 KZF')jZ@t)G2I(A1qgҴ75KI ˫kLE1|qBV$KV6ddLPrmqu ޟOG "Rf0?fN AJf%iP&A1$ *R֙1;vܵk BT*iC|;\4";O6{R 2T$Tq.bϦL @\Nfr?ZqjAvF(v@y'b-궺7ػsv<0TE%Wc{2t[$3„L3۸Պc.Cw=Kf3mkV. ȹ6(‰aF"VW*^?}ۄB0cZ;O,@aKD0a@u [,dI llX ~]L*$$&aINRE0}m,}+;-DPV&H t0Y) \~h`C (K(1K;wyリbGw:pyrD׶ ޹;w]7rx7,fzoL -tdf\d``⤗}2ե0 .A+aN+[|DdK/|~eeeii b&`{8A5B"Iȩ@…^b9A~R;I;rgZ] B@t@xttިK,yƷόLD^טpdt(Ʀ&PܳolxDEq@|^ˀEp(3@rJ'o?' a(`t;M~:~]4NM'IvR)oR,8Ҟݚ /rH aCb"G  = }fVHF(H]tyqumlj( a ͯo+uErwNL)}9plr\b{ngCA@GgN> >ًgZ ^,]}w]lu}t m;( O @H 4 +ZhPɡZ9{]wa T(I;w΂@#B\3Ᵽ+ϋ,|z 7Ӎ rfBN*V.c@̆<8kJx~f<Ҩl]&ؤK%(i\U+i|'>uAtzaq>_PfAD~JWi'qc,L2JZ/]Y׿vu jP5λ|õISh`| no^?E-b7x7v7 o3~#*x'FJ/(%+l돔\=}%cpXf]bQYZd>tӡGϞwJ Ky`d`Koz<1NRn1s+=e&Mtaa]ohZ P~{|G1fCM-:"o9 tum6?׌5q uO5sQ;`fFTP_j<Də=t44%5YP AD,۴ȌL"~ɯ~+!~M{oV@?ȼr 8nOtR5ugOR˴͗b9^אָx/GJn:91Z,brnٳuFLB@̜;ʫ2So///Xi,;\&iu <1GJkMscCoUEe7X(ZvL+hX-q~Ow©O\>u~ҜIzJ "hd*, W$OHU8!0 ݴ\.3l| ĬfYo4DZ38H:-z1edε_|)N̅bQHh (A${O^J%Bdibɖ%8DqaY{!1o#388hNȔT@(s\c: X*eYt7 ,J;oo3.5(3YED^TZ0PKnTQ͌sЦPznrP>6$ܱKf d&g,qg_;/c=i|2#R-cg2P$4UR_K;{oo?w{CiFhz#E ZZ@oTPƫeQ I/A:pF .Ԓ!rt%~PWG@^)ykʠ̰ATd{@2} AVm~INsB>1 Nnu0;q*V X(vY*w˸03GzGN$j@INK󋎨&_^$F0J:Pad ̎' "hٳT:TM&}\;5l oـ^걋e2`pGG(̲̂Vv$6]cJuuXBXckbkv\qtdEEB`u?٠o'X7:2Z_gG$jt5'$Ҳmb2)D2rO?ܳnu{w|냃ɩQ_ f)[礔?q+WZvLBY/(&ʵKw]]hw^YieoEݘ탕m}md, *a -@^׃^ !Pi d[CyDz}[z@9VQdY*t{LXk:Dܧ`B5^k4ڕB/}ٶ:A9 #O=xg=M+MѿJ Εe"uZ hpd/ˬY83=c "Oy$\[&Z[Cτ̲^\,r꭬3yB;m/{3gQ*8g eރ@Yjb+Hn Ӕ&#^٣J2]wqlJ"b@LN@H < lU@ B*eFX[_׾ٌJ7r?Ո- ޖ+1Z_K45Jyjwe!&Q|XA @I3#tyaif՗]줖Cι@{Y?+R'y B(}K#c|/&k2 TQ+#)oP d`d3gsRECp !.\NӬP,^$;nGDtR3aFq0,+_/7Zz+) >Nxܖ7H!ɬ> Z1{J60k0M%cM 7atԫ'QZD^yΏ«+FFDj˽ _EDᥒ88BIޫ ԁ(Is<3͛v999<== RkE@ #Jݷty|lLa_/MS%w`O_4RjbȜE\LWsKKis;t2u >0(JF%gxط-] FĎB@NBg>5lCEOxRa[[;Yf=R69du)hl۶XsÇv(כ{mrpr`ll0 ܹ5 8|ڿg7qHwAXN/{-ӧ,Ůggdz0jE:Od٭ONUk^BVޣRSO{ٗWכvϵ Y J(`>eb*jߘ_UNZ(}uӧOSv" n)-YB$Z`us h: c#c+ ř>P8Zg`!ԐB! n9\$EZt:N ;)P rs)>Z >y,CǾXw]3 vxd[ luu}6.vJy %EZe0 9[lho4%&Do]cȅKjp5oF[# (AXVV5/<#36Ƿ)G z"@c0j;xfڤIډǞzsT˘8 Z"0ybOX }w+›EO\ia㼁E+z݈?jm|\1$Χi):{r<OJj6!W> Ͳu;n\f zhdo-57N6G)c1$-j ,RN1>|7 ][Hh cx˷;m8CE)m"_ w20'otf'3w\5Z0<4ehAΤ";>84x]vt`ًgvmߓ >ScSs-V/DJ2ydCoR @\#xkss~ۭ^J'Bc2/3;"OāY[]]A@nCC:֗ۍ[78t|kx,,Ȭc`ֻ 8č4\?6df'm`x gf?\s_mx'>:Ȭh7g^?z~8W)닳>:Iv|tj.CZ~Tay\ a^"W[߉3xQ z9cһV|'fOXj*1_XŁs=۽  ڞ?wT9*tV/-CՆl*@AGn9G92f yj ז@Q}@)tKT((@!J°X|:3y&dm!1vN>3s) p5kf %5`MB V#!KKK帜3<#W(j Ωl~]*B$Kư5=CN1}#[/{=Iϑe0 |kyeiok4Ӭ5>T+˛Kp$p#F"Q^5h"h} BȬgzQm묒2 CO:, K%u(EPp>Ɩdž'ƣ £/6f_#SױNZjP vI%7} LR(PM,  Ğ;'n۶#wxճ2KQ|cc&H3[40R=t롓^t zƒE{ y(DӇzֺzp,Rlc>~7v4_yt!m$IL0666<< p{JIu]爈IJ-tTt (n@HP߱}9k#օb  {xp֧D`b*$j5\$ ʃNZFaTh7/p-w.AM,bet|+=|,/%ł1)~b7_҇tSX"u=ޅopA@vM/i]z B `f 0q箱|>,ĥz_=jjjk8$dgs26>Usطg?Z=ás=P I 0 A^`3FNN' r@C~zvA  ,qd}#*K6 Q)N}趼fãLяt{i;~hS(esK|'x CB q'#'f+#leg-%qȐJPf&`7<:t˝ŹFVv"P 5 3doj}K':Yg2_;xV@!]#oz H\.K c:ҁPjYHJtZ'jt;Oqo2!蒯Oc]˗/-,Z2` 2 euUP D.Z˝ Qc jR2ZJE #GTj5o}.'!QUJJRGƳTMIJrr!#w*J_84S^[( @OtqV{ȽϽ~nCwE!칒k}wHBJ*OCGO࿟1`Fc !dYyo'U1qQi]Fnx[鞇n-OĽz/ҩ.K-IfJ J ݥOَ\_Z.Ja\Ir_{f=ݴDN֌B(^d:,( T048<>>9H`)҅ByIP.LwB"R|rn;-rt#2R>N`F`\+[ڋ^AxQ/oA2(OOWgXZ~zPב]+.oҥUà9l6Ȟg'DfRcpa~vնz]1D{;84{O裏ݺgw~KoJq5 !/b83.&I rD$jޭ.۱#G}sYf ̤"ЖLi:c^z⹤H IώO..,W~̴)3w8~ςeA-AW" HzϞ9f+]~-8H(ǦF&nd?_>#q,1Xu;&:u#s~n^YZON"J_YB1]TC:IH1,ղ_]_gn{z[Ԉ`e A&3`}!8c/ Tffyi2 H)2 $ VZJtzC:MG^0⮱=2~`ӗX 4[/zFd5:Wm, $C(m.EqSDm,W{Cܾ{wle)daOd-Jydq V.ͤ(zx㟸AJч38л^\a_ZZ$e6xXZ-PI0,Pkᝫ Cڝo~Kj!;@E( /~3>ޣr Ez/d8&PyB0䍮;o.MwM|`@@XeEh١:C'AE@;ό= l+Xr-s'yAB@[WBD`_E^O b562:1Pٷ{jxtⵓZE_^j(ceDBͯNӶۃCSNM9k;Pf"W|–!LF;U`2b}}w*f+"s: ;L MB!e@Zkx\_>cs^;ykf`\2Φޥ忂@k¾}&Ƙ/844s Lx?oj#:(H-<) 7)19f!}^߯vd)邎~syG>133{fc'[! CB\$3gg\YOr׎wѡ__Yu$I|2҂JӀWs )eaq;q5ILARn6ڝnjnv'_W1 ssq&RG&@ ˍzBɣD\cf!_o' NvrJ9it5a蚉e49XǞ*SC#@ɛm#ck.jCw8;{/Sۗ-um$H%1I Ҽ1 [uk~[= ir`ߞOQ\E C%}~ƭ9k+dT,;&~·nMTRƘz@R*͞饗f.^ Pm BO}e^[N$ `b!\k/<|Cgy7= лX$; B{/p,eVj,: u}oRmFJSFy m.0K3/<|:01ǡ_~%BBH`nh-//O0-,.=ztQ 6@v7PVz#$Uefʩm^7\,""teM7 d8 ˸'XԚ˭ϼ+F?SDZ /`?>o0TчEBE3&GЕeY}|CJNW K_R1kJ-GB!J}۞c#I=9>Z*3  (kTvOxzG{Iol|q9+jETֻ4KZ0jiC!dG2"gpWLZ Jp0܆V<"l /(jMOK/!uf 9#c^~ \e4#]OաY+**Wv뵸<:+ֳ\Y+|o}o,,H1(PRbR B flBägm.ߓ11)8ΕJBs{#~9_va6+[R ;f P kvZH: F!uSֺ;v%׏J,3&*w^}v[뭵6 Ah/\m{z 9T!\XNF9XgNCJ|~\15L_;{ P&o޵^:RJ PD5eٜKd ҙ7x!YMXX phhX( ?xsHo3hbK(`X>s^ eI866JhB)T6sײ&1)dw;[奵JYZfHi@H Ah~w7*JC}W}\!',JK( b42<92kώnk={v{ӗO= qFH< :`z/րxT^Io>R~S;&#Do7kG\>wチ80BvV敔(C%0 }G9 Es^(s@)ow{Xf/lt$)`%48:{<H pKW4I\\nvBK'ǷS)5W| ̬Bj'ӏD̮6#߷w2Dg$ݵC1(sz@B2iqϜ_o[NG,Ml^+*J䭐vCr95Pov3VY,BQ cTq>>uE&`z@~ 9tm'7 Ј@z@Ȱa GQYtJiEt9H1er.)@۵o/ϕʕTpKS#i'7\Nq)vS9R@fIO }$@ O:DuX,HVei`隌8 rJt Z@(46)),Qf4sĄ@!DʽFvN\Z <'@XA.I 7{, ^^tI.kk_](#4/ORohȰ˖-`*?|YS 0xRWi_IϞ;uAf<:LB)RJ-m*^s?*7cvKZff.TL|ƻ/ӼpߛBY 7zVukCn3zB-t]-*Fl K3|>'ŴjBQ O̯uAVNJ{f/9Y)MfWOTzhKK@B I=x_~I ~06hoȼGD쌑H@IQ?3^|ebtྃɱmˋ+)[AiW* 0P(~?~qVЁg~:Η@Obo`%Ξ;'/3  JRv}K>U舊Jime")$Z gѱj?049#2һw)GJcmz)I ZhxJKؐo4fPC`맺p:1/9ہFafN“ 6i$YȠ{RoZ\_' 71Ig'Ud. Kibh ]ᢻW?, Nm>s޲с>tpb%Ix տt:\&])&:X.ưly K1H^@emmٱ}{mdhfanqaуՠ@"50Ϳ9ps?xp\.}{116B!NJϼŷPn{VCA_]CZ(@R!moYcD>A fI .j23Y06Z DR6gӨtUH4Yu,[W'FJB;/8K 8 D8Οd\\=rgmrzB ]f8HT"yݷ,_}f< N@ҦZFT_Q:&.sBD:Hl*Je&cfYȪPyn떿;wGܙd||3. P뵴] ڱ|Oyӛ_So,FA<:<$Y R0a#}7OJ4nfG^H*Cj׾׏iK!JI)Y+yJ !TQؘ\0}W֖emfj3 @؟;F OnJ耺eri3N|䁇;6 ^H Q0D(ŃĐJ!ϫH )QHW\Yj6j:eBIABT2_( U,sZc=q} >xsWh}5HWKاsY5Q*20hvNW-6X2kEqR[.Wkη D,TJGyyyuY{V.aۭڀ'ϞP yȪ+Æx P_ pRvɄR+>1i.҄ҤV>Bܱ{ P~?B9õF}{E$`PkY;WtmeOs K3^~w*H ±wS;b^h_>H[z|Qߧ 7 zPu3"@M .bTlLE9Dt:QfN>582b-+Jt,)̴-kĹE?wKw}%u @Du?~pV!x@1)0aBf@> oCZY %3)Dd@WW/\/6#gϞ3ƴMֱ쎞?}åz_R?ݹk!CyQo?w>K3 FM=P(q^"RrT*7, )Z -()&,6RHHJ0Tsҷ}4` B$C!J)tN>{~^oԃ<;?w쩁$й*%Ⱥ<3BC/@k.bh3I޼F&mlI3z4IccE,<"*c\)|n]Zk}O~sO[b{,9^RgZ'"%FLȲc˰۵g/̯cog {_]_YH]t1U2>Lf{gϣ켮@t3|ӝjQ(LHgJHJ2%[eyvv/yI^^ ++8C8e˖%YEQ1 tq  i,,.}|{?bG/% s tXJ+r|gڹo//]ezRJ=SJ8rE/n??ZZ Zћ*NicDVh)RH @ 8cɋ_y233&Zm*3P(i[AP?aBA!ϠQkU,H2@s6_=qx^;\ .s@ړ!~6dHbT*\:{.է_}]mRDr %+tL u2ܺ;Yjf@LSQ`k7@Юmx gb4u3uo}}&_!K_zN,aLBB@f2Z]G4ב|?X.?OV v "DYΌ&B%>lnz~fabρ; ՊZ:|޷AwHVb/,B,2 9f#P2@8@mC=t]w _4߿{^9tR|*Wů/,޳;>=c=ұ<7R#R2mҮJl 2JX·AHk-LRIvLD̆um&s/~S ="63XgŚ`B@p͟!"#ods0ߴk: ; v텹T2IP(? ?~Ӵ>xOm߹gλNmnv2OPx~zTB3gRJww2)k?,@^_ R骴к’T޳JHyE?JVa|?hf1zQ2#@(M)*eD)1M@@k0Ɛa. FCK]|jQ0@{Yc.^;u\qMhPpF ,!}G]v|sx$rZRƪ v<|"3Mu$)4qhF'@L$SUhΞcl+u^ˬ7]Ddpk;dž)@jAȀ+ 5kџ~g|k[ %LUjޮٙF{җ\o=g8y.\0ys;ϟEeZj&6{R,^;z~˟ѿyG_QgCCx_9G6 \#ݴ&⻺3 FʏTj:8SWjJ=_jm_ 5́{*IZ /zgJErҕZV$f~PJ3B/| 3Qh =݅bV(L^g,\˪٩hd][FF\9s "@ H.n񇱠O9|<Ćȁ }iS(f,@㌔ ) M{}rr4TcX9r+2,$~O:09ņ[p!y׉V5WC"T(WhxM;q܅fy K~om~[6l("[+V0 R(5D?^zex|OdQ{#L+XgOWʥ 1kzï'~GǺ^m۶/b˥M655ډǏ?WrFn#3zՈ{A$KЁ ,Jcqqe?O5V둢#߆p)󵼔ָfU5uѡQ/K%&C9 bOv ,UR*(^*bAGf#NPF//N{W!t ]aPRײ)udE1 DpG-h7t/\z;Z9B_ 02 *>xGpᩓpsO1xW:ۀJ(ABZ#Bb2\ l]QO UipT,DQ (ںukWsV*  T*i/-V;ՖDjd&C->=|mjM/u*ZǸH.;]2uֳ=er:(g:C'N PJYIIƯ|#?{ࡇ\\w ~j3/xr^_{m==~YB;w읟_Y_ٳŅï={BvlKpH'ݳ9׫rI+u^ <tv]eRSכ2,4ꡩE@ Jv܆`9B -X'NRKe"!R+BŲ2&b\C1,IV]w*;StKZ =0-+(`q=/^9r{؉<΍=5~e&YKhY 鈽gVbtJ\lwOZKڜL ~U|df0d0شe1xřmRSETd\ u(Uo,֪ vC:99YDaX)[!PvlHn޼λvwu?3޳R ʞu ұK:RP;95a\"zEei߿}&Fǧ.O~WsfX_vݺmv9koNj+OH ,RK!@5.K2!T툙1˸h/h4ɛ5-v!LLnGG6ܹOTqhhYr ȁ k,$|Ykmr߷e՞M*(P)bYXˍ dX:}W$)X#(6 {Vo_SmV"84[,of-Uy܊ }{w~A_X8y_ڷ'-a+)&s9X IOf۷ |nYMMMCB;*CMLDu~0̚Cb4, m^c', o>M}={vz H @L\lA/c[:wT :A,pgΜ~ool߾5j}yXpD(MbBWuU^@$fFZf)@QY̚!{6I|'܇|lbܙK]YcA+/I/,AWE{MM*NR!C")yX+**匍TPTv9usKD$RHT;*kz(-;vęXY1܀j ˰jOP*Sm-r&[u=yo~YU.G :BD(sNAz̬ZZyg|g,c(*0!-Ւ(w޹%By݇ʻO]EAu~avfz-nPhɽ_7߷Oa7|mv-\XTJ.-qGf;/..Hs]\ D$Y?zc=,iz'GG;qu-ɧ|j5Z=J$M/M.ͅA,!XKa<ÂZgÜ1ff' "ARN=-cWk{exBY}Aʳ+'{~҉C޽c@Ͻ_>O~Ge &`BH U'Y[<3e 6'HA!jܬ++˴" D&b'!0yu`>OԮ4<"5J_Q\3;n’ylG_&Dљ+e˜I!S3JbSOCNVk{,=ׇ^oZtXȠ0'gFJ'|1J28C=zHoNyQdYۧbG}}B) JzzC{fvzJo~rs3竽?Z'~Za? Ƿh=43;>? AY;k'/E%59D@*fUZs$ B"hsNΒ"LI{ K-c:;Yu&eb! H#^Z4:ZS /醙m'Ӄ3KZ}q"ΗWTZsuTH鉜, [h~~FVA^^ (A;e9]_pN"@*,M*NlIDg=ӯ|޴y3 8cg&fů>5t$WHe$!Ʈ_eC` ն;7gXE(4A+N'6%fN9YCVW Y[D Yg}d%y3y8/(_BPT*Y* f!uYwO]11v ?XSY l:Zexj:@X3acm4M^oi]i4Yj Q9ы/+巗gïpYjBގDk#pV,.-}9qngf#sU, i:GvaS466Vtj <c^Ge@D&LnV$Db hs`[αY;Winj[DDR:$Q{sT. <gALR)"h͵s4sje3`ů<ŚV1ܥ k`'p7Ca{~wyrKGe2|`!ibUsJ=&oHAB vژ\KЬAk|z_S)d OiSʒ'/Ȣ;_ζVFXP9w w[~h4JrFsvv4MɁ1.M\_@Woɯɗ^}y(rţýY}E2yj<Ɯ }? 6rYYkZJHX g- 5n$Ψ`bG*\.tU_q`jsbUCRãO}Kw맠/ILn;[OT)z|4CtV#9}vu2R3X`\s4n@=t]c9Ii 8AK.71֚F^'4FZ}oլ`"&[8}9%}IK 7-0R()%H!:?{20w9T+~8R#yF* )5fƍ|k֓Ƭx3Z|E}O3s*|HkԀ0p7DD}! (e %Ii{$c}!D=Yg㸝綯P(TfgI7/|O|! 5Vf?C{@  oݾeemQoeX'C&t D04;?yC ѪתˋBz،֮pey܎W)0H `1 g7msf<bFFUbBE.^.©WaxhO=a^hT|ԹʇAЩ1cr#;hi)w;!0QѴqrh(&ilay?PB f6yn`"F)0jPd qdi|=](>IiyO3TM>E KO=?2D]dO@O7 ]0s6kb8q b~]5ko*^o d)T>@ @Ȁɒv̌3%Ug6\+].;IbXʲTWjֈ@O};/;q&^<^]`%ܱssw9$B Y&e[izCwYZĀf[ !} &Mo;zыQ w$vr5(G6'4vc7yJKj֒tN48\=-Ziw*{ 9b3X^tXBfTЙ@d!JT|شs;x*hQs`רX]@)HP _|gx뾝_?4t0$HZbwȷK52o~۰[) s=stKLrySZ0R[gq;˩ 5@$TZ< Uo=szff*?//Lw6 oCv^ڪu@ %ஞ8/6{;ۚN/4AV.OYUe"N1xWilF]: ?0(^IC]Q/>Pg 7N໿;vI|23cQڈRKW._KӜJg]&}OZ\1H1oԘ]_ϦIW9?* RIc/N ]]]}AX(I:"[D[m;i)@cI\J >ajE:\:2~KzfŬ,d~ڙ@%܆ن%L..9Ӭ֧x`o1,R9sRWjy]]2)t mg74{e"= ݽul>u߮<67k׫-vL ray_E}F8;5vnZ]8",*c-N-$I<ɝ- _:ebʙ{ٵ{+0 7*LĀ?ỷ/2d|0n&&A[_fkD̫+(vٜu1<<<ϴnI[pdW&Lo . :X<@̜[57\oFjD$p2ag_]x [,@ w0 ͩH,䉡Dy}:Ζ([t"MZ\<:t蕓'O˕Jaq~ ٦ԚYN|}^c>*(y`JE2 #{ccC#B(:D֘$wXC@~;r UX MQ0fX=C̒Y+&F3&T: e\J @C i-h lh`9ߘ19`߫E_ܫM.C#@RFU`ze[b[Q$B^mAb{o=.pƚXzG^{}aw܊983ڑ;޳es%~"J%q0!0ͤTlATL#'~3Ii꧝D@.>G.ږ@Οpߣ]MCےiMi =}hMgJGXv{p|=;*Lo|-t_SG>,ĥ(!(%٫7 8gHVHWt_ouneF9gg/B?{Q%VD1nčr(Y"j7v^*͉Nn2u"I,+^}ttX^ Pjfեӯ"#Rޞn"`H)2ZmS\b@ "4 bHƢDW daeW(m܆Re˦QGM^;eȭ 5:t69wu)ؾk֝ۋ1\Zy9@8ӳ3ՙ+zsjHJ<]БcKe! A4<0080nvN+PRHk%,-ׁ=YўfI)u-;348<6EEgZRܲg;K_?'nL"/,-~_ȧ?N:?ڷu[q~*qΌa@tX@7*=NEhɥ6Z* A].}cbR1=sC@& ;յ}֮.y1HIff(䌜VIj@QkKȪǭs.mej 23"R#zRX$ L]8u} u@@f e+˵R!%4(C w,4PO1\Ҹdn SJZGG;__zfoC"fB v8'̬]bP]Yr+tRbCYgUB(sű /7QZ)"9繐s\ 摞>핕~W>d3iJ[w!7&>\28xy.ZsO$ 3u6IX<;- EZV3zO{R$Nޞbγ|d$rRV)鈤Fk@ ~o3wu+o֒$:xhؤ! 42U _ed`Uj۶;@U7ͼgg{yīrR(S.WufrLl&$1X?H[3r,`$H><͋h֭W.O|h;j}PZYZ%\`k%H4d!W۔%U tLhMgUWϪ{apgu:3#Ȉ9pr@pq5R \ߖg*'0~yh5sM[BĄE?35:38b9D1B-\Pצ'{+ tOoߙmlQ 3-qJVg;6޿BP VU={a/t 1y $31^?[­ƂP/u;y~I/PH)vPĴCp=zsB䌉 %{2tǷ$) r -ʼnwyFLLJH1gJe mW\>7c/~gz/z~XH Z=Ի9 J3$}mӳ8-J}qׇQ|&'Bd_xĹ`_k@f2䐔V3@X;`rxʺ{⎑FUWO%!0k[ܪaD Q0@f ܁6fdY*vUfb@A"5z;!cp[rY[{xhw0b/lٵ?hؽ)JպlGU\F68!3#gm%y Y_yi)´CYo/OF@Di-ID -H噘:{@M29A:ԼWµB~W9SǷ94a`Nvnsa=gc\T@1 k[I|ySg'ҙMG_>E2eR>y)Vl??EM+sKZ?oD))<JIŽboorۤdP(}e!g,K `$N0VBh9^nYϮZ @bly$!9{e¤N iY 6gR6d'u"9 y!|˧X l &)%\re|v>0<^aLXz~@~ey TR@Cf'N<5:Qe9sIIv< / 8  _ACmj/5Y|]2T 諯<'dvH(Y$dr4J+pYIn*Jw 0 2dz@߷@cf.m9 bbnO mkkY']9(mĉ UN NM2`0wAᇕխ +Hjuw-./%y6:<R(_{q;Ly(}uyQ/"$.Xct\Cȣ/%RT)+H w&".P r"iPMbp̙Hcݾkd Zr Lѫ]AAZckͧ&yώl_weIWs1C3$/Z4/,ZOPvXvjf-~G?}ӳ=݃*zɐXm^I/|I+, I\מ<=\[wo)u-Z[F4kyV^9tW(|isccNsZdއKX9e B$Q^ݬ7"vXly'H]niyJqAwڕ/Զ oO=4y@:ӴˇNؿ2>s.홯O4{SBH-JfKʥrs~Iu:U&JophБA65_36Cm"s |Oϼptan14B, NӐuWPPݻg`nah `9]KKY FܑZG^*o `ܭpHA<#K3J*HK) RR naA #!BFHʑw)g2ej0y+гL4xB3 <7daz1k$YGþ3C͝>;$C=wn3K?gLA{w_>så)vRoE8`9~o갾")47U_Yd "$ۥʣ}=[ǸQ)MӞ&r HHDOy>fC*! Ϡ 3%}^3nOZOX6.O͖Gno8SO$V*Oƒl\>{e$aoaԞ}Q׫GNYsn`955yrR7=59<& 'L 4M}w*?z-1H<JD@&6;@$05 4 @ً҅-m|]5q4JkSS=/׿&f/{By3 [+$RP 3㤧XLؗ^frjbW[rzB WuoyAI@PXg3*)B9Z68{'^>yA Q YF@9ۼDܶu"RY6juZG} F b}#8&!D@M+W%`BvV'GKZqW=_$D`Ķ=ܨ/G*zRDoPC` !9Y8|yVᆪvO.m{w=Z ;PBk֛Z\΃fn~v*Om*0lgX8soC1oiUN{,,{?Wk8pI[ #Un9&nzZ\B}xI\,65.zz|8 R>" &UV]%sD~Wz;Y7%'ش3|`ӻIo~t?0 Ru66r2ȾR-eť/L^pw?gP}{U̞֗ܝ] (Л( 9vzenF,*zâeiwؠe n@p;nGbSN0\%:vdc+@TZ\}6ՆZю,\$1 ^;Ƃ=7˞7E_dR%Hrʇʅ#/}/ΖJzY͚}Vf_zyiږB~2|ZmW` GǶl"E jg"θQǗty}* WSyeb$Οle(Ԫ@j %L8 _]yr0,fR5v)*f|W}S;Pc=%,4;7qeSK3ST6~Ssxyi D咖QAjm]ڼƌD|c)N vlBZLb} 11tNb+N>|`BZ]<Z}p=A_1@PT"3|?Χq (-_3؎Ο-]>9B!|Gغcdʳ_s.;~ԥ~);vl76PI CI-b35'溡~*#0. N Z^[EDҀ77GߗO@^u ]4͂0\2{i|˦϶[e67l//WGGs,, MSRU]E~|8mTrtp[9֦P;6L`\dU(޵硏<4ҹ_2!SyAJ@@"B_*͕:Uې[Xi^B I+ͅ&%Էҕc g䘜X HBȨv@ԡc`75k'w]7^hhVG%xJe6)<ΗR&s t,0*O#mݾ_I!_`oO}_JRmXh4__Zhۭ?'W,!pQ(ǝr @U_v#IMW&gbeqo6n\=~ݻjj6߶cϑsH%pCpx҈#rvU No`$@N·qf bѭCݕ~ڨ3<7@ݕҎ2VKR:c]ťsgL;g&ϱ)w׮0̛M,th1l wFA240WI2R! < B8ىCq8!v6v 9ésS߆4ZrX28ց%GRGOZkIcAblx K/?w~yqLe^u(xګtQNr yU;﮽>3s1UP,w3߲$٨^IJFWWO_wPŪ}-^0۬)%RD9Efc<7Cbdx$ɪۺcXj孨+EWJIܞ,2 |*kIݐt2!Hxrެb)4.WykOYƀ-,f};w#@*qC> RAn};nV1;%UNFh{ホ*3(qY@PR8rD tAWzmOԚo[Pž[=O^lL#*nƐVuP~6Ibuqy\̀] +Y`: uXmf FnGZ,(]ZoT{4B8V@466VzЃ?Y):cWBZ7I% E.(j˛?ǎ~8է1_޹sL ى,ށb&A>6jf3a7 $ёMg5yI(G~{{Z]ڽwWT[Gz ⣏thth~qnk|_ ;ʘ YRtņ^ aFC~+_v{ga@Cȭk.i5[z"hSfZ0>5OsBkmvmk.no[Pr(WM-z1P~קÏVǾBerX[aXغI7$C|%|%\T -uQlY+R2Bw_/d _(GĔqnԌ=twҎ_~y4ipldr J"<ϬAhwtYs&žwPJrq}eiJt@Sk=/|}amXK gKSKEggi ink?J嶷ӿ?3?vBV]O_n/_/8Ve۱YCT W@yz@ `GJAH@k ,i@^O!bT #=XjyW/:Ѯ6.?{$KS;>kչK~O<Ð"$l㕩zܿqoʲ@3~I;CWwמ'6,v `"N[fH7-Ёh߅ƷIIH>$:KdmT{{m4?T.^]t nϽWI?x؉CiCT_wa|N,4]i{J~㧗^# 9j s6Wy(Rש\'K]Z{#y;o,>"n[rB{-ݍ=v+ϿxPSgU'@%C0$ (*DܹQBHD&.-?Yv容V]Qf@FA CC=ZC]|^?t~ FdA0Y$ z:u 5hQ;W}!_,wPzL T>Kbh|=Fi :PġDKtf3Lz jS)EF`5? t C"^kBeǜJO"t 8+@os%=s /ַnݔłG1)Rae?ټ rxĩdsN ):sQ6-sUq^;L@&gX} aQd$6W/W˭xeRCVJQTR Y+ Po^g"u֎M;B+sGJ4wI,I*~)D kO`J ovWaA+5low0XY]aڜ09 `ɭeYTzW_jRJ J,4*#14\8|J;<874x4_ R8Kc%4ҩBOQ -:}B \EO(U*ֺu/wvR)Eobr^%:I CQ v|m,Ԇ~@Yyw^Y#8fBZJ[(^tPw&(rw4D(#򋫞w{^;2Z#*%١{xf?g-\B^J%.h<MFVQ+܉k_{C;GKUJf>ta {`N4|;/]24B(L,P @[Sʂ`r$^dv`ڪɅ[WC ,mc|聙'k9+,[>y?IlEP|8rHV-=Yؑ@#F_7FAZKkZqnoֱ 16WJu]*YO-Fi\w7IV-L:Fb SA #3+sىv00 @q=kZHC!|!<&E+ܽwXiU"oà[yU~ 3 ’OmZ6-F)ML(n1_4@$4Tm9zERJ#-k3qKޓ_tY'me~gnFUXŷ V/qr+].3'./NkS +2dLbYѶ^ si(2MwÍ33L(PIK0G 2+b[+/U_:λz/rŎ@ $-`/(LĹbRQaMlۻyx`۶6'\M δUI&V:,֠k{!0ƫ @gO~_v J_S2k; RcV.#je*AٮtXHmF P)uxҹfl,CfŌ@E$?rJS-,뎏CZ wQ@!xs8;9ί B?q})31@q`t .e=6"_o~B*cb0&O$ͣa;g2crk<!z+`\*(% %r,<%<+Y?g>#ⷌQZJ3~蹅$;75 PZ w{w?# a_kB`R놓nϋGHAҲD1{]wsqz I)/,WŔSZ2 7BL;:|B5 7fX7;jRF&,l_l^uHBnѮ_M[ B;PKze.^\: wK+RݺD-(3]t M ˚ {>h/Rr"oH͙rܶmdcϿt4%+!`R\,iV (0 Kzv#p'4ϐXߟ &9=\*,$iZ|`Ú`r:^<| W8ׁ0.kvcfn*硞.`ΐV9:9wD꼝۽oqz'^l$BR:١ZccKƷloKZy&k4I:.o7 UD%e!*( g QR%.q 0_5^?}^=+SE(x` 8c ZFA!P ?692Hrdt*ݑ_mWJ7QoMvw 0 .z#3̲8jɝ' "@B3)DeSe]O{mtE+ʔ(  L}kf@)l3Lϙ}^kU[o3 I4 f@Rά;}g(:VB -OuJZ+ԹB ,>I,*r,7nݸ칉jm]j-o+EqS"0 :9z'' ]I9v}śwmR&$ezܢ#z2gZDZK~W>)Awgbcqq1k(޵}8HFD@BADDYnӤ+J}wFվ#ڍ%p]6 -NVP5ؠ L\ۑv10g\f: $5 ӏlrX$BF:b!BPH8>sasBVebMKC=s޷%IrR(4en7]?/toMYJ2==ԲC:;33>? }p-o <1}@a&?,]ك-[6ۿy?Wbhpʯ$$|Fa%&9 XT`@_R~! A)#'põm[|Pt<PA TvR_={~y;+#Ã|eǞ]8B,Q-B<3 FG8SQVjvaVsSb5ղeeX_AFp@h s@>\?&-ض]wʎMYs#v# !VP0(X?p[nx''hkzqv4A({ґ kS3BpNAȬtqxnډ9~S_oٽgg!rHxv̮>=};K\m#YD#l6fTJ]LlynŜٽ qadty3ǎ5[bw(qja)ٲqĭ.\섔'KK#{$et;:[ tMCjvb`uXVi/|2۩\^+ ]\9uzVdPD:ks.b="3x{ L@Yfm&){ 0 NZ/@@,ldY60ЏHya(<3sF{J {o{F>Ȳ<=/R\)3z'z+ːZGmN< pZХu7*0/Z4"\ ۑ}eslx" B`@XuaQd)x &phq.{5R{,=wOѡ`džOo-bҚ!.Xg,ب@! bTvGgYkV9;=Eq b|+_8 qT.3g`hhhvf6In豣ip rcLRYXX`Je`p0 NQ\7xdV (F.46:&n=qt}i"-k*U-ꆹAA'`f z+0KddRv>\~$[n|_89;7]IޜoqIŒ6Uݼq}DEZ;;-m.hU{9MwR)GBmċu׳g~fxfSheh͡`rN׭X:z(n68t7\ˍ={4xキxE$n" F=Q,?/AX "WT*$>rdxJR+E V &:Q ( y 27:O.?Oxu/OOTJeh4 <{ "ln"n7NY0 r|z1z`p0IW_llx\,oKLC]N.6#0 h,Pnm@!w7R~ 4 >S[ih\KE= ^ɅǛ!ɡTo[T<)kڥvl77$y'%, CelkXXV= )6޳u3hjc{޺yG&ff5r ?3qA4JqX%Jm$B'#iw6O|('ơ݅٥_z*EC}f HG EZ 1'g@.Y,Ŵ 2ֱxpi#ra rW/ ??BS.gl;t5VR̳Tjq E4AsfjPKV!4Hb誒%B닿/]:.u_E|@DPڭ?tpb棿3$,Մ(lp瞭Zp-j"˖$k+osq:dH$"gDI U O<;7%o޹I+'D+(YDD@ofLq=;PǎY&;b? O<宯T?j?RX r9t9^#yHJ Ȋ4sKՕrߣ5DϹe:PP+m,gL\ Ѕ8ڶ}SgfA.bpuhr* *$F@dDDCNf6VUjB* tx Lu# JO}F~68@ʹd *qcarb9O3PA8@Ѐ~^",n'Ks9f0?{IyY钪P Hx}y"9vQ4=9940V NũzjAAiy}}m$QVw OppVRqllT޹gAI;iJ! _O'wk#AH)?31F 'ϟgΜ'O|"D`/ H5E@9͋qy(e=䯇,%GFqt*RvmWm*}s.1h\{y}ͿBDE0t]WT!˻qv8=xv= *!"y."ti^i(]`EA`?WrYL;TVnk.hQ*%Bt!AhLλr r=EP߼0>7Wqs`ngR0dzz;BvA؜ Q[kϕC1S>837>89}@ (kPT ]S`U%Sz,=&^=^zqȃ_/je>uZ}zϏ~Re^c08X6YqPŒj.K՟_x; N9Żƫj5@륏 3/:<70xݮoy2ǟ [bR(-;[(iص}C۱3j6텑P!|* :X@<@OεzڌAIR|x\e)hZ`dv+A7żB& `Q,b(#pZd& X34$W ("\V_ln,Ws-6:483;;8З;O=vxyQR=yb-&DG} \ \nuo>K:tS;z=qh"ժ R"YuD_=^+W.?/ Rk^F< _G}?/uo@Ԝ+G<  ;։^(-\TkE%\fۗ:6GIo4WSj%g{ @{4G{-;ޢ0 @'Lg^th^ye]H^"[̐(ħ.=v!˟;B5*ƢRj٥ca+\Q^aYy`DPۻMt@g!4}or/KU ;w9>{ރʖN}ǾC<i[Q9pIO܅g\8q r M /r^6w A[o=}v^ejY\ˆW ^Z 1J(,̎/5C#$.5p JUX*|#Ͽ KˉV*C(6z_yَ3XYqPa|І 'opt@[ADzwF_h\^W\D#,{s/b t6@Sl5,][lm.5gz "eGhIՕ*=8RD`w"/ޱ782C 7kfT\!w]`$)oI+mo<3?jߵ:i;,2zah<Bu9L"\2#WX u6a^b|{k1۠.7"!Bf&T|Y3<&D°OQ$ 2ܳ;Bi@"" =g^Ұ|p 5<%)02>Pp_էdc6uJ /yeiiO=~GF6m\l7<э @EQirb,F1M7n乵, Srm';(Y"–+JSZ& ndP9Z z3=nP:==ev]& O<{aLdCz 8r3GN>y0RaA=٪%Brf1(dyfj D (0 @.@q@7mX{`VEM__,cag{JxhFg yJD\8uQb[wz{n׾ݿo~ixpk;R4"I.X^kqU^z@<=z04bv(DW{fКxFGSD "h FŅ 1XFaW T>ujn!:9=;f'O,1XVX+H's)t3ZCW/ ` [ %iT3D.OfNPa_*EJ!\cس3/-ut l6ZJj&MX'Dij9q )fڙmV;/vP# 1ĝ9ysTtAiJwhkwlsq0iyr=ﶾ, %P &ʏ?yϑga=X,^)MDNj.* }ZڄΑeVgwnwvY[ cB9x:JEQt#GUJȑ#y2PT0=H{^'|PL ()8s™'~kssG]O9ԭoغX^r  ˷_ # BZ@rs3C!)Amٶctl]Dk>M^V²Jᒕ@B}|0l_P#Ģ|4A:{|sa@j4X֌%t0*dDD\ hlߦݷ_[A$L,~nv h@V*(ffk}lOLL޳g޽qtoDFQT JE?044knBԄV+2cR{wcNZ}RGejғ=qG Lh `&RVXz Ko5h5;p~oyzϾr`B {RC=&ѿV΄5 SHy ([ϜgQ)α|ԙÃe'^G7"X(q˓L 89{lq`;]7z/='yf7=7;qr:F?ОѼqD(P1uے/fGᎱMw0kh{hLNC"^kb9{ߏΫ˳KIXdR{=lݿn- &IDy><206)xԱ9p~z/B0<2ܘ^8thv7lWdzaB yuGN>Ԧ B)ă'>=8i4Ǣ0QQr⋐PCAu['ӳs'_8W6(knE 3apC\[oR.,ԇGf1IY)eBoP0e o "T#P:rmm/e%T} j[I4P-G8@ߦ͛gNb6l/ֻi[Io e4*aփgGy~$߼~7 Wa^Gy!X[!0 !yٜ Jy$wPO=u(d^&}9 (3Y;F:-v,B處V<{ K'Յ%hIEs )w?".0=]Xy(`LoXaS͋z=$Y!PY[iHAPXz{  q!vDH]E=)&ibwlΐǒP_b{GL9۲uS 山sY'J}|D|6օrML@(]LZI{ӞmN{֫㇯Fm!HnO.fz/秦&&ijC vZa'CPy[(ZXo(.iWZ pά&YGO ")jw,.R*@H:DL24j|>MnM`;vZe떓'O>=U:NbQHyx87d)im]}&^ CmI@8Cçzi)KS' Г+ F#kZV\Fkaf0 {f`3{iy"I/dyՠ'Zf&bgN6NB"`j+yKued 2&dIA{1IW?2X=.@J@)Ari8n,O*0޸w}hg?e~Y-bl PgmBžF˂Y\utQ ը_"c2YYiR-/wuJ84~{ToG4Z3 hDР{?3FgrBJdX<8_&W?;&q]JivBL2|D&NǒB:e y@8D !B-RH3 >KqEᆾXcwo}o8|PBEAP64*f ӥd 7:nXņB,t\1_@߂vDMKݾ{O}'ʃ'&\jxEUP (EPd[Db >k[k}Od[EFg*F\;_6OCd!ȳ) x@AMJTf{wOs97ugwAf.akg4Yls9_iu;wl>_—H-hsTT*٣Fq`{@,˟xN@EZa F08R+W'PfobZ%|KWro4&œuPֱ3 V3dkQ[~jdM&*(Ry׷ocﮉ!K` $(,Vuh83U]\3~pJm7P@@E"-\hӨ;E|:kش}g$YjwɄ 7Rvhof&PٓB^&,?XYGܓx@@HU*UgC1QVzmYS.ZG:۱ûʓıC]{٣&N(ZUL:𚖅pEH!)P1Ovlۿ]ǥh+z]H`XvofZ5Z!J9 6CP j+wv%\Ƹ)(0OIib]׹0?a}Y}G&ܡK A*AFD@Mj@E~"awl>q#^ =n7?~7?9 QOb>cҢnU{/6fFnZpQR?5{ƷqӟݷH!pCcv͒4@,^[68800y[ox;€H)Rw 1bmù??:|{՘ ET2ks@YeU(e4FƝN_u6츹6{ژKY9Eo->G,Lj=70 0#'O/>8;lա M!@=+?`A$2mg\wfDYY79Nk{ '([ h6 (ca f=:ﵾ Ѧ,O|\ZVzU& ]R<"Z'fvbH x^n5z~H/7s9Ln < gܙP|qn 敩k J-s>0XZۋ:OىЦu?sG~r|Zahu(b2 +6KYsmsב4KF|1Jr:8] 0 cjC%;y|ك_}$ -ˊt;# h\N-n;kݰykEBELd4 bkW <9 r'|x/.Z,;t8g?aُo>ʖNld&BQDԣ)0x/*μԽyߍa\}6GDR:.}.LM)\n}m/~4~C dmI~FTZ1 r@l |QJWmB v`rX ޼"{ fe2ۆðZK ̓硞܃B+xB-Kg(hH3X:{$@oݵyK ːMmhZg.CT (f(+1urR00nhR(gQL8 x=ҳ=:sMVQ(i>f3g͛(<1g޹wˮ}ۋxjv2 hhd0Zׯ&EheFQmPO>ſ)mt {Z+J}H@ rX "!eבʘnR{Wɞh+j/>fL{HضJohg{xރa% @DVF`n=Ӈ^x]֮bU;xQ=ף2}nۍzZ$"1ﳗu&KBKYHoaY9q lC *l,OFCҎOZMIH\0 cL~pU KN }š]}%Y868d̂;蕢7jubE ;$Mjo|j* =:<덻ݺ= "]C"e$-^iF Ba4%kZ1"Xf`]{J_&* APڱ^jG0`ǵ>F8yҷuMq02#˿ĵ4Ruݺui:kKxs Ma! "8 h^="nPGh.@lKw񮅌. ׿ }ᡇ Z#cwx4<5qjϵ[7<އazL4<âHڤ]]W)4 :y$ZzXDЍXH3{nuuiCcI[^\hVr8U/B,VZ yV#8qJsssfw{_F`Y< DKz'LPdrMC,.žRpP,cSgz篾jgZ]VTCZ!!j_4:j=SYzڻh Po6N?L4wz"[hz 2;F!nĢU7@`\TG7i$GCe%@#(f@BE;ăf&.?j "٦)_wH H\΂JYRs<=0othP,LO;?Tԓoux}^gN'R>=vsTV ty.VJ޸3%ΉuuIA`^?Kv< "20e -IrW?sC.Jg@n;pm-ׯ]GgT&`dٹ{Dg!xL|7eL%.z+ X:|d 5t&!UT,sΩ);}z! TSuO (L OytdEH"B\)˃RZͽD+ҏ;/L=}H;KDEA /}ӪT~˻SnڅR:+qnŗW{!V+b{jbXƶ$uf[>ؙ7| Ϟ?}{WCv=,"'p%Rvu@;}ٹ%PTw?,~-)uVѢAto>{lBX HG#B ȁh{ǾlӨRii$V .O:W]9z+sp /9c`c"S\@EzZ>5 ! L.cl(q œ@gP W}|?-^ AL$ AFT/ܸc5dIqR?[]_4G@DJZgZ%/4Ipo{gFF)0 sN ԦmouX;v>%-,CjUBrv[o]N@Po̞9 ΁Hr!ɦ;}+iҁDQXw8pvvsI2 @=loZ'8os"CR7:J]kSسƱCɲb\"qw%Z2PN!R%C65B)%w{׍:ί }NonCY8FΚv~nbq~b,ϴJRTGƨbÄ|ҥ^^Zs2aRu9h=6:˲ 0zfGJz«WSo`B.1X_a(|92EL ny['&rvC^gJsT*]qEYi=奛UN-R-˭oxkֳ =Me8ه.ơ"De{ @ۇG'fxd@&BwMn&2jdqU@%0J+sOÑiDF9>tPHV: H]p, w r}}X.s.=`up?qZkhEyX&I 3rAT}M;v] > Ο03SԦY<;3ŀ a5k`澾~f罳5ѯ,Vk{;|19Π4*`P`JA@\e?4QrQ7~{`yXB`zٽ׳0ڄay}NF0X $xpр_ρ#{/(B}{kGl/}Ou9'@E,Bbe<E~ӕRYذm}Tu9z5k0 o{?S"@ $Ye-)ZEq\V/@]0 D5G MP*P0Z]?:{b4-aPܿIlx&vHR @A@Π^h*xn HfNT7"`"; b*BhPJ[@QJ0[ꯩ8oADQO7Kڮ΁ G=Z8iX/=,1bv;i&fv~Γg>pHփֻظs{>#wml$2c2xN҉J'g]Ywcu߾pGWc~,4D}-m߶~}>`ylkŕ4B+rCkJL>Y,KF4:|[\u5p57̷ l1Tz{2VEDkM(NΟ>D8^XXرgNFڹf3˲~G;y5޿RTl:ccsDZ¨YFje:qxMXηW}s:g-+́)F" gC "ƏlBݶվX4CQb Cn q54" F''fsDZNGj$K@Zʟd rLѡaB Q.bz:%MsA{`@۝HGŢ2_Y`;w^ϟ:=~0,Mi;!*y1%[iG[oƛnѣ]t|oά@$Z;^+}ݗ&tSJ!{Mm޺a|ܬ/̞9?~nn*'T&۟SywgyZ:_^~a&"0QlrხS({TS\A DqWھkddgy.\?]R]?? sFJwl^cۦϟ_KK??OLoۺmjzjrjRGGo馓O={{oi=Ox޲sm[cv]V(fv7CTY:J|_H{4XFb'> ιS$Lds@{3['|zt{}#=.sd1J0(?[?ŻRfy׵.LNg$D@D!*@+}+"0Bu<4v\O~D@!4o\=|t@L&8eq':!/EuRҘY+@!Pq_<6P,:ЂGy;փyt:* l; Jm4{K€ndY'K@iC_9.F&O*{>J+XK'QlV8X-OJ8zt_P4>+^ QžvkY_i&,/aaqfS_/DJ DBw}-QVzI22HVU~GF+Z+")a &>400p…ٟ[gB /KFn0Amws6/O<;1{&Iq/=qgl4۶y;uܹǏ]?xI@?\i{1 xD@to3'f?68Sem\-p`AVv ")0A+for&͞܃!ƱXjHZ!gYrnjgܒh:vvcLhAA4]rt2Yg:>9== ]H@ ,MI* #Ŵڿmҩ)6*"R"㞋xkmJ];6xÞ8l:"#ұ,; .w "J)u|pK¨HoxÏ|LoeV$ {w\ę9֣[F RZQ׸le9˔$}͇Y Y%2# 1?u##gfB*/+P:,Gwts8 bj) \ʧޔʐܲ-R*(eE~gȰ˳O}( @sJNjYuι^@՗ky#>I.}קi/}C_+URZ;.$kGpŊS%J7X &kP!xZ;琈qYk!$X3>fZt=Az(n?CP4@|(&a]5Bhg/L:Y\p쒬hWi!-{aaX_ &`A8y?/<ܣC k _;4J!ϗ&2 VV70&bw:@_/&严‚(lܸedUk85S-ŁtlpO;mbzhro/FA6aܩYc/V3 Rh%;olne=RFB^ˊv7 j5EFbفY/F* )j󶭣%EIʠ(O=k}C+"! #1/)\":jwro.58Yo['Z;{l4=/ q#Ԛifq; 5o r1 *DMg|?{Ruݚc@F·Hr!l !tp^>X9ٳx+B VPXirj5O>C_*עPOO$lSr^,$ @qDxAAR(LTD0dfD ;$>Ag#H/y (N|#>To/l+ZV* <dMz e(emڶ鹓'Ov&CgW Is s3s9K؇ N4{r>zA$ffp[o7jth_;Ǻ\lu_+jVkEfH"A( QQ. oкA4A%}7{䙅spn +Rbqsdߎ|/,[VDQ'MmL}9DĄύI@S{\Fp,H XNOY7ZEuݿ3uYHP g_ڶ,V{d5VPϲFDE *ϞI,/\>qaUnu!q!}:Q)4a\,"E*t;"\, 2RA^Ry9Nh֋`&JG Mss?g*w;]jVY'^`yx]›E;דs 7/~6<7?Rr켰AS% u؞VJg9W[><V FXLus-3hE{u}@xDLqr|jLS!(<fh7,H $xۻwg@ jrn> #T$H9AC)"?GΟ?n\bD G 2L&*kNQ7fL[ʁ**Qa"ݮR*(] 5#Q7 Kb] xb-ZYӅ:.-i oֺ-[Rņ2P$ |# "916|GzO<|'ϒngNaOEɅ?v{~S .'ڳGZ>ܘ9Bkiq˦ N^l=ܺHɳ,)uǢ(ZC@@bVKss.,$ާ??ow2IA,˗5MyIf7K@E鉙}GVԂC[{nE9cbƨj6sbWXV%/ 3 yBDFfk~(.|N ^9΁4Q0!n[7qC?`-QƎAEJ "؃P3)G/1O֍(dxo BxvZ8PdF&Bke9բ| ڶc5PoUlɳ~9Z Ba=EJABlL`6Ou\&~ zf`,{?X2F+IJ^uɊO @A]Ho own/ h3t{܁-7WkHY1[Y׏m޴GA➻D4Z}}}%s&,!Ҡ#躅N={/X+I("iPQ_9~ꙇY[0`,8(;q񓇲liAd:3fe! #`&'>'}FF/ދ Y: qeɺ<|ncǞ{مqf+V@U|ɾZa w^S4ߋ^Rž'A$ٰ80=aw/k rN1KiN 5dyF-S"TCb`NN8Pk0VȘIT$ZU7o|OP}ݶBUD2&뇱 )譒NN59&uatg;y{Hy_ѨO#"s]m*Ϟ:sϜw#BMosjAD" E- Re<;)  B9, pQ`i"6s(^daS BX| DZ*Ο;$I82PO;W/=I.( H Z  gglqu B!ТqGWj(! Ke6f!&Ӯwz䩻?s#gVV5%p(@ :)T#vں7Bo@Hy#B8è0??3=2:4;}v>H+I\@B_,mۆVk͌Ts z^²h/Lkmb/Y7F](HEj4BTՒY8<bw)DQJwJ|?Ti޽)%ޯD驊_"_uK0gomq욛p_F'@O@+vؙ{~GƎ82}|X˓{M@ls3޵:Nҩ0WxgB}@(e6mympqk4U?{N獎.T3խ r ƤSQIl$۶{ӿ773>(@Q9.&ͅO埅'2<]UKV|I,fM:2+>Vex{jLMlߵ\.Xc|^\ѷ`k& I U4}zā玞OYQG$==T:p}oo.zJ!"_+ ((UT9Ν>i"t*D$^Pb22:ykX]jwgՃ2/k,9y] cwYUquq왓 @|  N!WHΝ[GG_8yQ_,UK<f qt5YL_sOvAÀ:yյUaԓw ay [it?uw8T~=  {h膡& @A Q!)g"(yg+3w\ڹppʞtmcLhDDeAK>'Ma86]l54 32:4e=]$:ah8S xDï?l5 4@"B8y۞u/AN+c^T2 VHBqavg/wZE"aYQ FºuiKMmVk[|/%_Kai.OMhu Аږ aXl?/41Iz鹙]{4YTЅ`UL`={yiv7XҶQ6?yMV)ճG3(-8[Sxi7i qyNջY K lL8tHAW]?0a_QyIAim4{M$apVB\RQ\ԛGm0( ")(/? O>CAnЄ{s*Pw\ZlkgCWҳI"Ri* )/<~xpEHs?׵7D4I{ttE7 yB((ntpġ?夽% AR!cK>~G[rWF^zdr^Vf؀Z*M_].W)㜠BO+3笪o{v5$zɺ0 ؎`X?2>=znZ-s糤fa((:-k|*'( C!fyNF`|믹pw}ރӤ;Jk.&Mx_8xC?8ʙJc7G_kb斑]^.U0/Uk$Yl΍UF#e{\ t؏x)jVJVZ ouՑ_btW#@_sbH#{v4ID8Rv48:8:6O=q?7+PFCEFa>9n}aRck( GFŤan3c/*dyu Nk6\[.!_]vyBriM*o{˛&AT,3SJbep ~}+/NR#="M4 t\+TIj0D$|Y.D":-`]r.Bsϟ=y B"Sh{\m;pW\T(fr^̅Szw"ATڱ{hS6s2`9Y?{RzÛnݻ{b7]XowBTTʐi_B Ck]X|rۛ6 чͺ7҂3W ;smwy^+VA]FAL9~d@L;~]O~S_k ".{{o覀 B)bkf/u %tLH^$e ͅ\Y ;I T ƈYhZ&cI;)creY܃{ѽ9@TzX-og] SS׿=r =)[EȾ4&8K(D~t(Ju7, u@cViͯ#s>lhMOL `˳3Ǟ;QZ$s,)9] v\}]Ɂ8y ^J$nh{~ss!zlzWZe$ SV<)R S/˛kU}u@wJv7~7~F0 0yA%mZlVZ*B\*eY~O!dIr` Z)'l~Ҟ;, R€0ږ{-AZt#kX\^pRB*^`% sQE(**|k*=r;D \ҀWJKӮ@Z@Uj8M^(X*q10]R7k2??XF {\?GE4 g͓W\1Sm$O<_^ ,/|wU[W Zf2(*;@sq q)M,Dv˛ggL(Ф9nǶM> EhmQ9 LʘPmOmoȪe)Vwj.;~{2U?Wk?V?v\Ok "y(8q|ZY"vD9"wR6cr"R.W##y?q^¢yvpŞ=7\uP[^\_%M~_z ?RJ6Y?fmw6OdyNC#u< 2"b*p yΛāK2dg/T 2.p+idR u UYGѭK&'N%IO,;_kkNg0PVS2|񕥜:hzH  8Ke`ABBe?wWxX2"~UBBF(``D{_$,ç.w*+l߱c׮]J8-[&4uin:?Q@k]=߹ee4" BƛRC#*J}eť.BD"EJX[sVa%3wQ_!PF8PCjo޲RJ,aWW2[3|@lUDX  QDD3 1ܶY^l6;>%]^NJ3GJv\.Qt j=RyQ@x*wHR)6ZDt;uoyO\i v]QАkw曒<B )*|&hXoMtԩaxdgs(SZ]7]?g~fa~[yƈ/uՀ@/QR.;7ⱱ׳+8ÂV~dsACjYY(5!iiPPnY-ۗ|xr;9pHղY?ui4VOy߻Ls9$DDE!mIrkũ@p|QC: 0);to7L9F0 $x4h] 3;f$,U+oy[=߽TW1("0 LT;?;qP=Pf{;흛>[ `M,urSFUKW}!! 80@'k{6%W PMF6fu4VbF$grq2KhʥϨ?—rMxM?M"Eгf@fŌTQX {)ƈ]/qQwjdgO].^?7?g~O۲{@C#qεO8`@J(( F[^jal-GQYA0,$ \8ZXi͊Rň1 ( +0Jڌj^i%EiCV1 ,Rᑧ^m(a3 OԇM/,#vn7q9&ϒ,|Y\Nn޸+”=D;_~^ Y\`Cn)lvMZvMq|0|摣@C(r3?sʹsjM] }˕֠5!s rn?~ShLۜ'|3DS.`a`uwc+͖2R%Y:[ṋfi;˳ ,!~ 8EaD' u%& ="0(|(R HEZu Dщɩ7կ}&0Q٫qڹݍ "03 02z)xr>&0#4@(~Jv|V۴yr5@'N/4\ =)%P<+dD"8[-w8[U(`Ryni {65]Vhnq_ 2;b=x&9$DDD SQSyܔ˵zIC2{xpgCRa~괯ڳgM77|obbbe+OG (8I;;O=̡ y1`غ_VGRP&!q^XPs1PpwHu5e]ӽ eȠcϤ@Ay甩}s튽ݱ F`@BO+:!CFVC>&lhibkv_eF%n 3K\Ayr9:}ʊS sGRcaЌa')M6Pw>Zi/ٯ _ 7E!RZ36 , 1 RJ+[^r&ǶnR za#iA苟SiC?k׃r왝V X~"gID{vչ}w\Y^'׷b0RݺqUWN<7 "8{3O}H\ICJ @/# 4:JQmrqiDjb]PtѩG[kY I j)[ t`/5} ${`< &R@"6p1AUUlk}=0"IFY7oUH^CDI?ėaYnaH߸G)`Eޯ V_1\ pUl 粥~A),a`ȝQw E;&n?=FvH1TrC6 m69?مs+9o.vu^+OfE0+Bh|_NB~xTB|<4? Ņ^*3v;_u1>)u4#on G!7VneY%h( 3FϜQ/ywxp4J5X5j&Ăd[ϞH+V ask31s\*!6&ͳ4ϔVݤרrh|jhu6E_BѠQA9޷񡉌{ j$(-Q.٬|q-$l@\fwl˟xmxtϛ +v=nͿwΝ}Ǿwc%0@ !ojM­{7\s̊{Î ON%c"ET l<arZKC%GoMhpURrrZ^ZƱ8y,-W*, "6n4:i7m>}b p\Ro4RgTjPN=W9wOs o Bޱ5F&7Sccn ' Qb]GO]ܲK3ĸ^Kx-.=uCx2=f݈б{dt6q~Z-=+c (OB:Z7]g_ JP x@`A+x}슫o*mm4%""@`\9=D ĥ`q7v [gO:&rYQz\|cJLQ0J؛Z#$AOf~sK(n9?' `-@@BIT^5@Yz{õWo8q$ ޅٗ-pHG c'[Y/ss3-w2̀2l+ؑ)@_Hwx0bY["> U4^0 ;KKڔL T.id%1tgyclwܱrn1zC``,s_7 !q=7KddZ 0 ֛@]y徟|{=ywO QrJQ'jQ vCuW=Fh |!DT1/1 &b#9,7WjL6jiw3\% gNL@ M0^ Fm=םO"!:44'/B}7s5%)+6Aa Y8+pرq;G];;ͅv{g|];CMv38D4A zi?Ͳ('jj睈u#97Aa/+wwK:*Da ?[277WՂ @D D@Di ɰрMEQѶ;Fĩu|S=;'#D%Z2skR-ȂG /aaP0-U&MW|9c)LZ0(굚v6U)K@J@0fшE~J'Om߻ 0aUC&DE,);Uƛ~\+7{fϝ659y%-v W1ҵn>wȸ̹Ť*( RˑAEtuOY4:^{FmػS8u쾧ϞnYyrz>륤~!)c@boRZn&iYEN/w{bNu۹ZmRE1&#pT^}RJ ԰*$L>f%m9H9)a:rfB( xBU׷l7H@53D+A[7wz5',9.괷^?ǢG.͜m; ilـ#zK#Hfc?ua عμ\ OusV2=6 LԨJ|୷nnySgl{Dѳղzjnk9|F'J7] (=׃7ةZ˥8P:hZiΚ0w㚫?^TLlGNkAݻ xT.]pdHy9kT+?~wͷuf dq5~J;uQ%p{km^AqlWz6˭ 6٫s[63s3"n۶JѴML""13 Tb8{nsk8 àEAT۱ D͜;g>c="iZ*Mٹ(z5++CC02#U$:Ei4E&Lmڲbɑ7), ("H(olIQXFt-|"h7#घ]:P zX[n~ύ^o!ctBc d죋&UC&d𥰄"*޴ sbÿOrYH4vW^a8Y/*E׾_iSBDgARJR6c6%IS6%gC"g*s˭Au8A' {5v_-ޮ1 fB ?HϾ(Eb)$O>-?p+wδ-y{KKJ4V>vgHaFfL0fVO݋weѽ3 ($3pfڏ ޝGYG{xDuªXYi~@1 HhAaff4ahB%ihc@y&L@&':޸ os++'7߼k{D!" ^講?w#`hFF~^O^se%0 2UEEΖIH.u2(4D7~5[khL@R92~^t^,842VE<~{|^ @j)< 4 #哻Q9 6{̙'Rٌ|N0]Ph z`!352yuW?gVe<)hgQAC%SH@P\]1Y_}Oπ9>>`Ѩ\ZIgg= a?޸u/߯o{ E!hƱ^ׯAWJ2;>:Z+U R\i-LLmr޷1{E'y|Gj@*i! |͆dAK2iC^ƆG7}fOCpJeG )De;4sN:|̙};*SG'Jc¬#y1lӧOW'j{oBc_< aXk(20 _^Ki](3}O= #eU \+OmyvfZbN֚4M^϶RP640Er.=_SGPU#0Q~/HHbA|])Mʱ#Sze)^H^ƸUu}܂w|gjjL Ai x1dpGD$<6*S=^_.BȜQ2R\rvݽkN.= `b::R]ZrO>y\)Equu,&%_^ko-V K{G?~(2Go}vS4Yh쯨o"xv w=б=u Ƚ,' gy7:c]w+6zEHRB`M>TP6{Tl`n/}K|>@*;5Ԇ-g;jRj Vl.!Z|]~ #( 9x'I p+znCd{ga2 PPz].\@G|Eۨ;p-Λn =[B@*+|M\FVc,@ |KqQMuS|GP HZi-D}pvvS8 yBBEc:1!$oti,uXv6C9}nG}վ` Vcs?B" "՛'sr\wIZݺqw:%>8H*By0@}K_GޯV[Zu7zw+ AvطWurGa Cx1ܙOg!H7KBfֺniYk nAD"U08ǥ7ܰqCO8 5j& WJ*2 me{\ D$zMq@v(JP=~͞SqDh1O>{mockX(Iep۾=~+TK99@y޼u|-,ZgmxgR^VVq_=|f!HA08EPTfk? F1*½GY?0 $a ZFãAL|m(<{v6N-#;\J̔bye&݄L(Ub\Ib@2ePr~o˨+ߨnvf)9tvc1>6U+n_fA3V[B`E#Q:pET%ct096i<ӟ?C xP*笰{b#;wAZYQ",oܻ-Ղ~VT)6.ڎED0DфjNꥐT"D{OZu]]PءԶTtLm[|+V Ga81PQuvlj6MQ!WXǨkOJA?xO>eŇy^ɓw>:2E #*)*,CFmT-fvΝ)\nUJ\O}zٞyVX0JBvp\O=XI`T&޳!D2&';ݥ4lٺeQ@ UA,kM2G@uNPD%U#!Zb R!TQ*O&GQ[b6)`/|@"P~UB%]DBM!\PՂY,|ӳ3cAj85659H`绁FJE^qvk^ᙝnw~;'*(<m@dOxGn+[(;V~Xɳk)0,lY{WO=$mkYie0.!ޑRآ6tn5 (A8<(œ=9̡=9B)80 M7u&5 zz gYa ^cJ Ҽ/B TƤR;{f9v8"eAF.,.eH HTL/AAI8{(HCc Daf"^>$B&cN/>r[y(FaS?W9Q@m a&쌢r!gvn>0e˸ufIƈPCR@7O<ԗ)cٸ)xM-_0ePJL>f 1! l@V\OH80{.Zu GfaZ$~ QbIwdSHdX;'ƛ~E_?H&&G [cRTW7 ѓu0\b!VDj:X9c&E}.?ȁZҞ;-*1( 2Pۗ[nk&RJ8R6QZӲ{Nsx()P%4 "zf ܰy=;W-{Wh幓3mرu_R %yhzY?p=ӝ;3o@YP2;hfGTt "VJe J<>1\?KÃ8)|~sB8˒\߲P3(fcҎPFT༏rk:# rkثN1QHƋosӵyAZWxvlll-?FB.ĨjcacBwCv UFϹ5@J *F`tM_L;+?IFJ ܩ7ޱ^  [R7Qή Źky_pdifiTcjuJiT'{Yښ <VYBGLsDiC"+"B"B%255v54#G9y8 I;@R"ÞHUH̱.{&y~! ;RbNRau#?O3zIvnngR`rɹF 3ɳo~Q8 \.K/3K>2,p9@Eʦ.{>_ljEWg(Rm;YŐ <ϑ92n$iׇJcCz1H]O1/VFaH8M! (", f rVRwS'V}Lxݴoj9|-b`SNO\}*rָQZs`]nsΐBdTwٱw߮[n|O;ɧ~om ݆ٙͅ4mew2NK}/qTjc*0 &dl;O)0^/7ګ7s$"`W}( ( Ktr88 5^\"/A/\/֗i/. sgY5Bee* vt[\1l۩תq\Jz᪰^^NYTXB Xa P|' JriusXoM[xnHe*r͟IOBERʆ r?{PmL ! U P ѫ6-R:\j7ĜWI'#a@i_D<2VliCRU#IXY ^FBqAlx7yDLz>'^yCMxDgsьJj۵KO?jGAIDAfaA`<}E#!T C@Dzlb1R_k=Tðr}|8z76KM=xҫCKH\l*)<M2*oA7Llu3eT VF7iMp6C""Uwi)L--4VZ (U+3z?-/g']"]]62>~rmGZ U`$5&jãP:[:͕FVJ06mZqnE{'<9Q۷Ǎ /z]>\cGaG}HϓX\p]^jMi]+|9J 'F74ԙ3(p\ Hťx~{ucozW:8<5ށHh {IFIҊ ?v2iwգ?8BPL"9cO=؃S&n| V+R~8v `/,9Ie(l0u돽!,JTwMt)=tJYM=8H,"$IA$ ^n54Mn4z~˛6:S'$}@b@Q8Y6hHk@WEFc RBz~W5KV\ 0(^f9'JCR+^J&,,7^ W#$a5Ue }g~=3s3Vyei422gWmތعwd"2\ FsgVLg{GA9jh0dy-|]j mE:I3g | u2 <ϵRCC es*:.W!PCȐȊZoۚ9fm0`@=Jk'Ma%K̩Eͷi|V%+V*Njlذ{E &}WH(@JR&*Vo_"Vwðށs\"gλ 0=/P He)D&Dr{voٰq5PwwO48vzA>{oye0J<;H9 =*ԫ xa^h^H*>m>ZX k_W+^;vlyqqxrUnW#\Pתu"bk (Rn7xh@U*҂.֗;YX'|ryy 4xܚzI'M6Uʕt*2ÿxxP5*sKGw׼m?ӏ'RxIȣ>҅ê46< Nʫ|c7On bD4.6*c G?}};ʭ$I(D ţ@ PbCn-8k@\uEVDtz(tEM/6t93...S/k9X/M 5TJn>DFhu*G> GY!ԇTNmk~R6OxG'a10Ff"Tؕ⒁ ܶ}3" 7^Twlݝ 'lƍmiTsp?j@\ҫUz`欚 k`Jdz7vLWAO<"hb*.IYWP,<,ND1^^n=SDÆH]Cw2i` hck ۼi+"tIE@@e~+_9>qȹ uP|e$ċd8XǢJ2 {%@qD=B =|ϝV[H!n<;pɲO=q CH03;۪l޲oV=vmy;(Syg2[iuZjmMo|=;vq*< ]ԡEg`k-!0 rTݺy[g) H giE՘. D!yjmn\"Gܥv!VGyѹ@2ZB}7LLm& RmTr9wmRk: .}x9"7oX; `J%eQ$]pv HauC 9k2CZA܎+ֵ<8XO,el ~=~W?>bsv(~Z-j_}z12g?{;nݸ-]O}D):"@1dOlkQ7-7zS\2{ϷI菖WV|mz[[~^wݱgfg6onyuY:]& RFqkoܾJtΤ}[W"=[6\g u>(XFkd DZ&e1*lruZ8.έsxA6j,wZrIť zxrhB]cO+h"wlonޱ[ET|VFP-:eFx|Ĥ`N@t|p$_׿ ]H,dU0|F~L>}쳂NG/8n{W0X;_fȺh$\[[M:2FecӮ%q~zz2/^XD#@ݎ]i-;vr "\q8oۻo[T}?x׭A4jv% eYskrT1M׌4jZYmݲ_λ1"s6aY)ى bJQ]=$i>><@ +J+%ٵ H` ~PYAVfqz?6]+t\U6muAʼd.; DV}H;:vkpd:^{˂ ϝx4mr&6! {kRČaP,;vMM>y%ڸas\}ơ#{h`Q\?}=H hժW3G0.SpNKC+Q)5[tEP)u^r7^֡Rtn7n_ x^ƒd2sIB>Z[^Lw'HmE|>f>tQj%.ؑoڼ@"zژSO;r {MxcemTJ tc!tv&B?<Ɔ!WޓW ~$rY :2 B%S }qݤ#:6_Xh;6:kIJoggIOH,ŷ3O^].@~G^λv}DEE .u[KfX"DhZ#)j;E@ޞKo.LpQu9 Hi aM(7:Q%ɑ;z"yNh%n;\nF=fy-Hn`|6 JƐ/(ryӞ(BDD$ĵɥ/@k#3L wS4/~P<:GAUߊ[]½4ADFykyWrܻs˭pT*߃w~f$z52Y&K Ȟ\ qW'+z %hKB#g;:|H*444:1e-;/~dt*iv;pyq7m30;Dz!@|~v>b!P@OAXw'Tn#a$ɖjmt !|iZ~2鳽vOGp1IR)Ɩ,C̲<"0*BB5࿱HuhM66>nsL,OX獺&*A(}45%HGN;Oc{ǟ  H,,/Tz0Gy De9)+vha uf~?>x~/]hsH&'n|h2,\G V tA`(ѹ^H/wDZcI[!T696yter7*^Dv{U,3 SCC+K爜,*){ z?nAԶm1%?U1a-3'/-hJh,<%sI"'Rl 7Q3#+*O1:@i": 7/3; S#CGGG hHr}ہHb3];4U {1r@"`Q8Һ!"hL1FgsFe9ZGaX 23!sݵ7g  KQ)- F|P@%*H*+Y?^8u]QYi`a΍H#*PYf/X~HH  =vD3s 0s&?w|x/yEs tgyc<|#N@VW*x\χP(N Ms=ٜ^aʛqС_?tǭM|tA3eB}8Կwz}_<=1*P*U ]JVTKAG|7& P#6arbj-!"4^!ywM]0O }/4oaGiJkFfAQBD'@n?JM #l.GM7li6ۈ@!  B[FPXqMj_5Z{'@>[/<RT.W{NaAVfZE v6'%P4(*ey@T!^Dzy{ELB}}ίΝ.K Xmo&14K&(Uj(M|:n]$C0D.|+\XPгέ4 \>CR޷-ƫoMoHȪ&=OrU`  jCY]wλɅp\Ň'N8sZwGoAa5dϲtnXlWawF֊s "T:].ad-zR[؂=wsODJdFa= & 1׳/>w?g/"NWaE]t~Ǿ ęsGqކ&%<˼F 1tׯe.-,Ɨ>#@˥R';x߾n[a[{R%\А}faJ%ha1 3備U6"\Ȑd@+Dlv+C򅒩@ӭJTB9RD VCR9;V[0s.@`PQL>iMC,9U:mB:r hй5 W*14p̞/A/F"ݓei0t N:Һͷ8~2V)śBd/g\" b򗿼\)al<_:]9.vBPޥK H]D!H^ꍽ8Ϯg`uFzcD<t~Gq=D,yc<1@y?0*!+PAsGq9KX{:Tڲuffn~n~a7DžRIFK_paTp9=VEezz>Tk$IZqW;Ŵ!+u}{=Ҹ\fMz fvבi򜈀$/<-h{PwuXpMU6mPre As\RdSەFlBX4>|A!`Mا?wGشk05q}E)\~̂Ck "U4kig9I,ڄNy'6n[l-r'xA QHwz5{ fDQ"9MPypzu=/kK͕N2?U d=9@Q_~\b4( yt-9Êj}Z"ņxOOt;ًxGh9zEȫDQecZU*R\>מ!if Vԩ]{w$Z a,}$)X^AKXTcǎjԁO$JR*%KUE"(C❷wͫߋڽ’1TbW *OEG"E5DNɧ&(w@+E@ Hʁ6_w=z_b Z\s.p.NAy?5 !*"ώ~f  ȸѱ֥0,:S'/ L )[*r7@}1٣C#hk TǏ|O>ԑyEjC;kFAH">[iF.lmF{O,H+t.ˤ^D \qJ @f>.Ka%]$n WQP-.UAubDQe˖JK\TK g.* zID; *1"yJ@Go(]{v~g>Š_93A.sH<{?J%VҤuEwֺÇ^jlyOpÆ HU@Xʤ} ) Ә.P*,Dgr9vzjchəzmd۞>9aY(t 3*Ș _IV7 Y{K/3##QY*F$Hyf4j#RsI #S2Sl%TĐUn2?[Y[}PRҀF$*ڶ}&KaҶSq핵\z돽O?9PJslDcD(/+BDgMA*lM$H'N+Q`PksyURX'='!׬@ϏLsDbky駟~ַ\phjCCndp$(< v{ yWq Wb:x I΢F}F++~yiJqyrbP-,,--0lѴVΞ:iDFHu$ͽOFx-(5 `( -Xq|ie1GʉH[n|gD.a0;+@e(e^*136TPԢHbӄflbzzxwxyak_tYނ۾lܼ oكO:VN^.@= +|۳T{rH RdGȉU+@5)蠟O?tgfvFZ,Xy 賏3ʞ&kRyN j/e׀]t=`ڔG;s˥j;c6\msTEN ]Ϟ^X\{Þ-4nT-JJ5!:T\h s,2*E htn߱ٶQr\ M" kf5Tݳ uw9r %6G hAX~ɿ1:NA g@ZObJO]hhhJxY\^+rR)n3T:ɩqxDo}-cOcUp H 3gIfl?M,7PNR,,n4"sP5m%dՃO<GgNbL#ӎFu֫vxx|rbӦn+QEaD nQ2K}8Gs("yA?12<6ivv^}q$?T6jMeTs}DFBk]9(+a HEZ+I)'O/8mŒhҋ@T*r#ӳ3n ?wbw[Y ߫Y~is9뮻??~jOL4U3ٻȅcԺIBͥHs(;qny'bsUӯyw;QZ{x8pZO8IB2cEqw!t_˱u_Pjh 4.0ie&ϝ[63gωZX:(<h6M[8'9WoyƐGy*mݲ=[W["HHD S6P/kţ#H27*Y˓rPHfwCz%+d_.f 8~{|^Y^ܰqP@/uɭ?Eˣ<}NHZwG.׽H`B0!/Ő ^恂r KG*Z"\z,J9|;OSq5U9@iXuBDϬdN}Oοݯ|@ e|}}W͕[uuPd6Q(d:oWl4W(A(m3N;K*e>]¿{_uZF. )^6%,"bY3C2ŷ*aFDa@Uۛ9311V++݉M^./XmtPWa>jm:-,0pvm|{Sľ!]z+> QX$\:7P!mU^ ll =g qF "pur>ݹ[Z" `vѡRsL P/X^. b6ylfP?hX H3jgϾm;6[q,[J:oyi/zPHTH)R rbtR=}VEVͪM;Djw;~?r >Cq\@ ,Tdž_Ci6Q9r_D?8 aZS[w "uD;#D{vkWchg?J=Os1BX`Ν]o~|w] JzY_ie)$U/G1wg. 8Ңѥf΅*O<@t z&p`rcV]8u1zY\ 0!h8Ҙxʀ:"QU/~'MHC_{;>~.XRÛ 7N4=O~/h?U`(ku]Fa*^6/o)kUessh:AjqݮVW\qu}rvffl16>흫׍9y&''7|[{eq{:h `9HTβ%[c{<~J<,~|iG?}URڿ}dzccwu}ࡇjN e2ER%) \e1(NsZ챩ՇґQ@@D[a兂Aev\7vABL˜&)"f|כo+ 6繠oH\I  LH/kk'x(?PUJ^C@HP;x`@@*52q]vpBOn:Jk:!4I1̠Q1k5E+Z+8JRSD$A-ݤU<Դ-y$1J9ffzH6&BG9H(Tj|'"ic5Ykpf ][Z39F  2B}E;[PNJMoI#*EY|]aH -2 pk fʳdՅ)kf&ewg144XDĐqٳ}}߼X3fYwO74؞4_4=2XRv"0"MݭB@k ,v"k I !$(CH:) ڊwm[MPqĦ F elz#x5"WPHGȾ $#H9cc\sM6SA`I4qBFDA$j&p\ iF$6VkjjB4DQD(\ic2AF $Z $/cp׬tZ7cmA#;.V  `toXꪵh:1Eza#m!MA 庩#!Ia*/7Z+%5V8>ՒR)JEugT,GP Zf{ -_~ղ!)HR D":5aVy9x~Ƒ*p!o?7H&( s8rͲ-d$i F\#xs l%Q+m}>1!%?$IVLb@2ѠlS`KBmyS sP/8LJ%N-8AґihRr0vR=2*xJ( iԂbƬA4A ds& -'&ݺq͖8^ˀ^R;bc]Gfj;w<;>6ZCJHmT*\8t0g-nwi[JչmWmuUեXGR3 /Hc޲U Tf\>y|l`pZU+stY " 6.:j JIw8$af9 I/d%mtTتa %9i"br-.4dbb#)T# +JR= m1H9|ITvIRF"ǁ C@\4psiI@qS@kي$/7;slv.L#UjH A1Xw~{ɤu)YU](EXS8wocfF1.t9-ha\>8JuH/~rsfH8?% Ȗ.z{s)ײ٬f53EUdrM9f4S.r2S `Fwkm 22AB8#Y~)RJNq™6dN^ӚxhLm󢍛u>F"@>Xdm;>i<=$f@hN _k8Nvzj}. Ԁj>ͩ6z,V*͢/SRBFpF6³;􅿛ITgnMβU=b$H^OF"$QsElqu#3*GFQJ [o%6U8;mm&lG ڢG=x$`*auZ^mPR], )RRB2R*MS"M"ٱ6ry {N)ub/9/֚B<'rV 8һpqcAsxݏo$m7ny;޹a͚S\MH$\7HkId]Fk46'v5ۻ3aCRH R•8Nǎ9/|cm@LźAv;l)mŌJdtFCJ֚4/;:;3;{ 8֭ƆQ1ŢX}ŊOM$yVo-nj*QXf,NPfFw{$S. 2Ebg+OVeJlb ,d0M/z߶88ĜLV iz2;x`6AmM 0+l2rEe˗ V-kjPLu%(R}t '9|mO_te}{eA@:(!AHS4E3͂2f {[w\0BHAVaX*\Rg-"n.OSHlٸ=/4lЁ^?ֹnU,fj5!4AI)d|JQ1BC^y/) $n-+lC,)d-4^[uHrj~424bqEZm77ݝqT!L z=׾ C]Ӑ>=5]@J%:ѤPyZ @L,o/t5lipu7ypľAŸ;1Xl,,#LJmjWXxBmor|'499Q* &I$n= Q(t;H \/):.:68>&IC&[D2Oҋj+1#Zo|V p w{wWÙK._#hV.=6ƣ0jq m@R]kˍΉj:@z隳5 0E.ݲ&5?Z_5ֈS4=MDJM oӺGGXGzu;y<$A8yҥKd|?<j م&۩uJ"+W6(I~weCS`cuuA\^&I 2^6$UPs9$l$ormmH˕ёᡥK;yddСclG:2B#l:v;rYO=səIʍ}۟8r'O0Ѐ0(5!"[0ӵ='JS0:31@͑h"N{Stpfw/~4') Vh t6c3Y)Z>y]O=n?|pW f<:wHWJD9u7^Vu)q0$15XStX=dzjnp.`NF'R\!)Hԑ,I\(xD) @jᄑ'g Tؘݷg7^xi*Z Blm3@)8K(~I~A#Bb p@ 9Cr^$K L(6 َ~͏ 09zȓO=>WZ:fP;fMLןCwǝ݁۷*޾M FC5XD4>mQyb5㣮9ё[ h#JZ9rٲF~X{G{&:L.2VcNvp$Jl01٧PHrѤ4[*9 z9YZ7X%2$ ,hiBb@8 >e(!R/Ɓ}.ie6_ne-)4X8+(65\6[f;Gl[|$ d`HsPZrM[7gjt>wT 4pڧyEsb wmy/75<ػ0 {#0-՗ ry FzogwKj}qbX,s)ixaZ?Ğ@b ŋ,A`͖!YO3y3Ε![$wYS^^ J>5Ձ- nPCF&9ǁ(Q\dn.ol1 ^@ H:;V!)KL>yK)㆗Rr69sBȢ! wT$)wwdHÄXssq1\9ą|NRR֪iXNq.ӳzuW<`ooo[9\*ELN?xP_k0>1ӟO0nX]dcBTHضmm_aw#$PJbD3iw(B̺9, `Bf 65r㪎v qR) c-7OW_^` !$ɡΣ,2# "l@ggg-7Щ4Ӵjbq "0֖RXq֙ѓGHq/w4U|jt@03%N^6gW;1)x1w rn@ں5>"g!yl)P҉9zlt3_a< Zҡ3‘*)1O-F)2j )96^yGzYHj )4jRR=2 K$vjj8Neؓ'g[l01U&ڨFOLjǽLo Ȓ4d2$_ȣGOrbhj/~ow?%IcQ0I^lV\ɧx iMVԆ}JB20xzpPԯr_\:f#ЃOljcY"9*5)(`yۤC"}ϼݙʴwf,BaWc*AQ=l3d#kE&AĀ/[vڄC$&7"/]9v5# VjQ CޥHmSN+ CxMM Hfi脰i ؟&FB]mAeRWR/ ja%y/KZfmwwRBnF]GGsvDc%ױ6iǎ~ ,ZJ2CzzEv86504\Z*kSc DAX$.#miu+kcW)%mpxlv`I?+M͖|&Zd3RkmRopbwOLQ9:l38/%3c>^t%BоGWo۶~=A}{OΔRdnJHl: 6@ڵs͖eTGII" )FR&/ؾKY "m,P1JKw(VFD(5*w0j˷IҨϦN&ki#On(d`H icwY06vXO=7띳)[DgZ&@V p%=S82B6o.KdEk22H 4 R(ד$RɁ[nQə$NB(Hq@ bXLdzf vfZS],򅂶\ˆԐ0)w1o4FB)zXiFэQO(Od -W&`fFg39~Z.tZ= 2IRqy2IcG9g4H^A{2`kMXz|ko~/|=(5LP$A@FHL쒋%WlΥVrN\&VI?ͪ^]ApJ=GqW̦Qǜte*ev.FqUȎE5C} E=N7UMj|)=Ju <HP ?) hT ghjf@05T;wx "(B'0]*%I.\rW -ڀ'Bh;xmk۵W4Ⱉ8x#`WUNM828]=K.Y1;Ǧg*7o|&/Kh6B"&䊆T!bP\ b&+406leFm|\Z4Lұjè9Rȍg{w E&IYai6p ذ R  J )5`>U>ud! X! z-hA>lfVG(Gt5=6Oxţ<{5H2CKLJ͠4ꮫ7 .\b[$@ u $J]OX4I"H qdcqj {>_;3pđk\)TDQ'!13$nrݶu/[ijgilO?^N'0:FLݥ9 4d ʩ}Ƹ ȂɌTB)4Br^E u~˦8Q*r]=O(\o*)#h3 2k2o*u}4йe>b$~9狓Rbe_U5(UiH%1c*UEjä?я] =N0 Xz﨨n2ykZ~Jv ; a[GQ(%̌~ߺ[/ DFkmRA$I b%gT(R):3 !5R(mLBj!T^B:Rd\*cxv Flc4PuI m^7􉏽[?~|c>H4(<"4P#_\F#gu$F|kُC\/H`dLl$B N $I"02"c%oo)\BcDsoZ5_={1 3nXd \R kijR!ڛ߲bbo&Lkpd s۬O6;\ łbS2 :8њ%`t %v=ԉzi Nl5pMiC.]QXu)^v[? ryR-5(^`4h-`l  % Afdd$m>~hw͢㴉$LNY~wbxxk.BٹQpꕂ$M\?&"Zc$T[A h 0G;?CqBxIB6r-[J[$t[g޾-2*E0`-7-|9OBOX _܆as 1pgvzw ##ۦ.gW*45BV>u;\#@L4t4{dĸjϻn[”p<^;[f;Z)w:9 is^loa 20B(P$~[~Yҡ-|-yިRtH! 2g+ N5z+4U+'F (D<~r3Z!%n.|׷#۞S/#fKМ4je~ZDV2]G}hS{K4(GزHњNJP4v% s**:V>K[. _oa,p P.骏&v$~^F@Zҹke}LMSlӆ4RF"16gOƧ3E5Snl{$]{f,oٿ_ۈ'L|71|TfJ0jH#7^~#+:(/M#ѩBPA5-Bx݆lcjt}=tjRAh ׈ dJvIo[Շffm IA0=KNч?wAu^w߱O|}N u-w=s{F0g}\lD+Th 1?pl}BdkaȄR)lhRxOQ-2 !`FT^;t$,q8cHk1]|yb! 2cyva4aIgbsS,dŚXf|qSO]X8lYCczsCWlBCpq&<_0МKQ58XTcGO+.\n{voڶy֍na24qcxJ|-3dPYҵSz!RVkk/ݶc?3K/Yē (c8I%q>#ց{U*tt+V0&J8Q\`% oի_Aͩd,W%DPgg3;?㐒B wW/,0Z?T*/~Quw=Љq,UjaXlb)!u_lϺmCd:\DkwQpʦpA`'!NS#f"/4PEQ%ԩᅰWE$ <߫5jXk-@I)N8dpp@vM})]X~^},uuE$D'5gIXnD@<~Ilk [FyShbIVjn*]JYC]׍8L;;n&__8>41Qm[uSbLWO7I H#F`cQqT#+SGw?!4+q@DyCqg{nMt骍K2 @цQ[/FP>'ǽƻUO"ͯSMstd|&bK!i ("q;ju"$Lֈ8lhkf1('g晭YX<\ ]ruH `5C'̀ "JN5Gцͫ?恲3jlY@0Zִ̈DZ{P?v1|iSlٯ;>buLdĮX226RmTmbsxt5BQE{~_o= ?{1)%)L=olۺ|æt JZRo];<^$!.$+fH xYU[JE&A`,^PӤ汅 l@ O< N, Bd[.w wwv0'"[F" &1sE0l5_&F]PVJZTj1"qڲ`,)UM(}We FQHE5S 2&v\:lb3\;=GS3B';$6pSʝQd(MLZ*gQmf\vTgZlI$!7 o!l}߹jdI`-6'kZn $!$ce[dAA'|߽?rq*̵/}TjUDa@ n%Kswx "bc)4qb44V(@DHr30)]p~PJAnyrz}{6n]Ο{z9,99o6-$f'?h y7"2\~@BZj3m7yLi}޸r|W7^չ쯖]g`NdcTWnMU<`._3Jv K<<>u8Bhxo7=3;g$XXn fcyє@sId- bŠ\o|'_>>{׮w]?O=_ػ֮Yf??&׿K1|ht`q `<-?gXm/ ᵾQ$rӤq؉2cW~_xltȑ?Bg}_;Bg[HSp:WnX3z oleːU)kP&"-^=0 s W;7Ʀ,狩 EҚs.jw$8HW-\3ԵÇeˣ;?Hr*@^<NТi+j4F}w~-1edͦW=W3mo,sIzsĨHQʀN[Dg؂%jZ+ !E0b m@0p?g5c NRmbH*P X`_"W#n2ҪO?Óm}BQiDsc7'jl<;vB ۔@"0h%$dl6޷aA]sɊlSQ ] /r|f$~k!W.rH" fz@|3zg{{k/$1y@g>4N?WL_}~uG}oڵ\.qD; |BU+ qbЕ-[jR!)S-O rb" RZ#'1Vʍl&$ ~Z_Ȩ 4oB2_{ʐĚTri3=oqn~ɇVƟߵ;I͒KS]Cـ1z|o!!i)D4Әqe&zjQ&)&Lm* Bk\8+sCO<+I!^$ L qbj[o{NVM)2sGw`{z2ivYG;_q P$qIپ ߐƞ?p@#XrF@8r,B.n2Ed-x5[upm2eLY naN]s_o4$Bcڭږw҅yt{vK˥jX ?5P=-B6NªP)DPƏ++&֚ G Y  t˪wm鲞-ynǎQH(M(^Z YڇdV^~YGWoG3-Z"Wi;AR](}6veE^ {r#0!{,n~)|8 ڬO~TȀr MLwQc"2Y 9|UK+ӕ0J"Ēe0(8 9^MRJ@zu6MR\ll(D*J}#@l-& p|Rj:Db&7j뚶.r}F67;kLq=Z&ZDNNvGtv붇~_1ǭWu Kt K[GMoή~G '6f$[v]fu&Q4q%6om2plbȖ-GHGDژRh͖=SQhe2J9h旀\^[8O ұg##pI9;Z@ləcS'gٿ{η._lѿhk ?3]-bC݈gs刺8wX}@s*Ϡ8 p*`.T] z^ɑLgGTOѠDrQebB DM4CI-5ʙ[[˟߼f* +6;\pt%_ ȧjPF67%37p#;fSyp!']vLVf{Q:~EM?xT"0'?n:m" lV#jb7-F /C> 1c˘hYs۳APd.)e`LM+]iв]VoG|g2 Q&ɕؔor-E/xwozب'<+D(=s!k^̥tƦRzҷHQ( K5*:'Gz)Y)[T.mMۈ3IGHċ'JԿh z8*D6$iRNSxEAA۲qRӆ)ZU}Wt.2|@ugLG֔Q%#|o~>ǿ'u]y{"I>M$ԌE%t1zX]TWPkzW^++yQ*EaP5SH  () xX\_Mo7} 0k<>-P P `p898Cj!:H"c읿kKj3%%|#c_w_zJЁwCݼqɦu˗^s ߾b_ҍ5ClP`⦗XHK9m-jsh9xCkּoK-7'5ǵc?wwnzÒ K*\:H2APUh%i~_ >LA4s5oӆ W^CDlZP2 $ڴj/ O}rw0:39NCd$<}-7*J΀wyKzͯJOoHp啗=۟+/dӾ6m y%:qme+4)uYE x,=k*y*V@ȐPHT)i@BOL23gf6hݻ?ϭl[zHONu5Pd&NS^' 3*!n/H~"2$`nimaMNLxikoCGQ266^&l>ڲY@\X$ u*gz_g&Q>%Xt׍Cgxbg>ˆV[y-WNӚ"Y6/Os;v<] c!w 7nw%g?Sn3`s" rL1B{"jߺ6 -7憇W\5kѦޕpC61^ acZdnMa_iK =C۫sj4;ʛo֛ç֨B&jv3ꞟ_z=h.y[Ε*B)PMe*m j "+!2Y @&6r60KOm&%7q? SO:;u=zM^ry/ٵ{3O=)=[M sU++ٙ{eˇڶ+.\o\>:t HDL,JJ ~~~I{Ч?ȓN4Xҹj̀x^6;zqQw޾K>FJݷ+N9P3sSmH2AkR8BH0 3jnF5[Gʑ[FՑ2\Vhx1IT0z ~_}o>wd=~73f- $j6B#y v)͎oʢufXzž8?US'ܓg|7haH)CMkVz[f+\c֯JDckt0 b :~5SƂ4 A2B-(CZt۽dP{W&ry*Svl߾kNkL !&:&"XIXp ZwSʅ~d[6I9Jɓ^\ f̈HR4u3wÓHQlxiсw]x<26gȘĴ5zjx^FGYO=O;^mbfl%~ǛL #2`RZMNےan0!7]d贳r5V)SM$N/|G>?ÿI~*;]y)(aBFI8clZHH qR F{{VOUE$eSDf˖(O-Rcn^wsOsO)pw؄ FF8d7U4h\qoȡ4tM)|kf KVF.gz;5=ェ9gsͳpj]1tGBHD9@Z# Xl X$1"lA[cp EbLäg*TcI:5~NJeV^;햎pkk2XpEa񃇵 ]tí~]ҞSòAV !Lm-vE@tQ-C%&$tC$^Y@:jQlæ⒭2NS 6qJ;>1z7~K~Q* p~FM/_k_G{|PXP&]ysK.ۖgglnՖ4Hc<$N;oK..iڦ,aXW_|Ϟ>F3ok/w3zAnr+[v}u>+s9ǓÀ(4V7IӅc3I}\!3wvv ᄍpxx8}}Ԉ*JᱍiDQ HGpG/rt -k~?[W|>i˦K8eu3[KLM\y5_ۯ99,*Cj:xK6u}@ 6F :8%x^S+!05MZ@fv$ @!f (uH GxqaSAX\8?N>Pmx)<9"@{٫\m7 +zeShR(' hǞ> B{'n}۝tH&1Z$ ZSe$ی-A`+02/D4 YAh!$07|h`ercڑ􌱱t2HJ'J&Ҝ=/B8fL&IuZU\iޫtt\P!ߝ&vҾYɞ /.yDL\7y5W]{g3O=~ELs<n-1)m^Ph @qMhٲ;N# m:ys;Msg'/yNpq~e!|H "N>7^mv 9[7o_š5:akz*/W^V5Lh6ԒVFm4Hk @*&$ /W̝hZd-`*]e/T>O$bJE;1g{e6ĊC+_ [o.S+<26:a;xh!!V?aAFtҁMק:o= Hh(.CV׃޼8n-l } =C=}kѣ'\+qIvJV+ AD"Mҹٹct!SJO1>Hq k٘X+W.dعRcz&!cYk:;mEV/Ak_ @ҩHFoЮɚ{߷n>fHKHj| Ipt.><|S-9[>-uK׬&i/Ei ʱ ʋ}=kgړ2޷~xOT]EVd vwnD0VZ HSsM犙l^M;շ~l\. )6;ĀHbж[JP4„cyD?Ri*%Ihm _mk7#Z =ۑ}j?xl!~9-)UQ /]2v =؀NYsA\n}UwcYNM7>>o}oddl ]!Dy*OB!?77iƮ$-ɉ 6qmCYd\1H.!-=sBMb6>>WzFDNyzf;ۮVOj*Q5t3 2L FXlc4:R %;h|1/'ʻάQld[3@BrD7F79DAZ$Z)X[xë}BG"AB0r>d??٩~s=+,Z%Jh]ـmW w8@l-lؔot.Sܾ| TQE%O:7ܫ K%%5k:d Fi!}C&hѣ [/۶~Wd3Wೢyz1J;x7^y֍Wo|[MʑqS{3/ kGWo Rjwk1BT멩\6֤ QB}n܄X`p p}f'ks7u$:96urxlʫ._GǵZ\Ik#KF'OntQA}k>;;cm?r-':VF-~gHx=q{{걝#'-_|w_T\.-_L3M< \3GNڢDz}WQ\T39QlNMxJRhW &I ":9#:ŒQC<Yd/+Z#r ~i<77SŶb{6RҨǹ|EVƉFq"Rvtłۨ2~k|@j+ĉ`+m9G=l xN o|w<83ggߥi*_[37=({h ۿ|=9 FyptͯZܖ¬+Ex6n22 ?~1ήo-3cʤ J!j< H@ȶIyk\Zo*#Ǯ哚y=:GqTsbVMqFB^(RsEReD;݅-][#*GN׍fH}ǯ&eBg׾ВbAo7§zf`p;۷Q,8rh`P-oZ2uWZwj޾iDYqfzO={\{6~$l488̎] 9RGI )8nmDQx!yeVlD|uz`!ǥ|>j(8no+:bQp&Z8W+{ZEr5gf"FB$ZF6t+!nQk@WWynSBEU8aK}I@(H0rew}]ɶdOG%&Yv}?@z Q@⪍k=E(' z1b; A 0[ g1.p/z `4nlG7??ӈssf=#ct\,>[֣h\&#j:Yԯ~ltjr+khvn^d0mz;Xtٽ}{;>. fb ?._>8\qxqbj1094yٍ:mZ @OOOXhGF4:zPK%-7p[E.b\HXkq4IgfHI )(Iʕ]7RJdFjgnBjX_1]Y&rO(Uo47&}'Zu|䓏>Qlr³E喬]jZ , 3K=Q_|atd_ c`Eg6_Kp臧FEfE碐ɰ ;_9B1[>44p[x{R8 ֡;y̼wo~0 I ?M$-( hNcnM*Q깙j[G0"lFb&M "!wA%b3ᐁsХD]wO >Ѓ @5,iNkΤ'pZ$e P.YEhv17* &S\ o (ʷ)^yMd8B0FCRQW~n݆;4>+!#$ڷg8_yõ'Gz:FclER*$,] sD8^G~&(-=AZ:|K"R)kphq鉙rܾ c0&q H7[(&WpQy͏H`֓o~~'6_ |aٺM^zL4`܂ޏU <#fu]2543;/+zuH%S8@2cv6#&N<'˳#O|0VvءX _Կ Cv8bW09 $@$A1S%Imk[-Wd+[%]_ɢ$D1'Da"&9T>aQ==A"JկzNs^k]F83]j'M9vsO<02!s;v޴jM|k3A' }tԐQ:!'9n$wbb;w|=Y"رH]q7+WG-{Po{5ׄ2N=TFvgT v$8NHFXJ/u C @߈v҄`g@V 6f( A܆]vo:.E([6oIH&"6nǻ?13pj xko TMcR$^Xt RE"[7V@Bfck‹_WjuHBηi0)8%A0ִ2%.>}#i(tNcgp7tSџ#N2'(`A`#:>~HirA_ #; +No?8:XZZ,;EY(+zi8gmDO:>\.ʙs,*_gKrr2@(:ӉJjb2Vī.yF!\'{~AS!0*K֕s*u]ñWSp#aٔ_zGVA̝;36D s%8@f;02 2!H@g HtM[5ʥzkD)\;;dICl @ z9šC2޵k@'P#ʱ ڰq}PTRaKZ+!2s6{Zc@{{":+1.(j|?wdsJ_wϐVX,Cr9uj,uw @ioqR%:]4X)7;4^7?rZo-@.20C!U;69wzO_\u36N<Tșgz?YSB  :-$<(&ԘꅉO|ѱo= IFuv|w?Y /:M%0ynGXͨ~0ꍌtN0jW\BJ)~ngYxʑ^GؐiEf W.?];heʥ 3WK(TR5 xZrYZuBH!.y2љN,ز"dYw~m'yكϖ=%"Q KK I  9vZ,5vujv% wXʴ"W+ ,"g 0 J {D $"\W?V~CVKERg3O>_x>WQz"'B\ND@Ym.Nj/$,-Gh")34%1t@,LZ~笖*Į["}IDf&۷o*e%3XOCwݷ @Y Ӳ4u,dVNiqkL0ML>7~OU4گ-/~_-XE#E۷o_FQ츕V8r?~047]I+62Ki6Lѯ=OL,@5 Qz,|P/bM_#1,ˤgZ6QBRg&>?W^sI~Ûo>m}[Zܾ0!bgkJ)d  <"EshO>zG޻ooc*mf32km)u*0 ťE!0Bƹ(5[ }K˔e!ct˝9֒2e+s*Dk|okˮcSٱk;q@Ogkrq`Y_(D7|/x(c#K<$uX;|kq/K.s$glV cTmT+/UY?h:S>칱l{zKKKq{/kv;[@ E@oR"c\QCťjҥ`ms&k/+^Z Zk!tx'Jch(Ӟ.$,cyY:"Bivk!|N]>Lw>+N[>/Mi8^EYJRw FXcb^G6?O>/;:1:95eꪡ^*ykר@z*,y9?xsgΜ36nVڱyX rg79[RHRJtkt/;V"ͯXy%/[vJuVI'z (pdtI` Ϋ`GzFE43XH%^݉vBR''N(L۵7&ŸJZEm&^?+ /r>K;CUWxa@Z%E+lcg_& ;\vܹ\ ð^m,.,ؾVCk "j!1s.Nve=y!QLxU 1@DiU3jUo=\F2qD}pUQ\icbi˦-{?}mWo6!<'kW)ꗌ ║ΐ;" 4A D " &֞ocl&b Ҭ}]ᾫveZ ٸaXJ^V"o,Jrf9kgʼnIS*/.-V*%!I J)ccS8)"kP\Y7LJ$ #xZ{ZZcȱRIYf ,QX 0# S<9uaaqqd`p̅ JSجm!!@D4M-"J%r7رɔ(ם~Q~(D$A(@̬gI`,5K WPr. E,IJъq Oə|ɧNc=ֺm#/7KhuLJq8I²A>#;656_ol߸̡ǚ5ݹ~5s~s~6VַŅp8eVY)j3brR @30# a*"; }+Fqq(=cg! >έ7ꔻ{r)LQJdք* /K> -0HD=: ;?zaYH}^7UR̳V)P _*Y!;F xM_)PqَB@O>sχ"e$/󘰱+NJ:ZJ.bfD| MVD *pe96eRص49݅;7u{=uNfk mjΕ\ffz+QXZqR*K 'q\^Tf 3˾,[SB ,1H[Y'P.WT11ώM;?=aubVm&v_\^@DgΜ t2 R։+DTh˃.9 S({7ݴGů|GS#-*!dg?5?\D;o% 7vatvnfJ4-s|ְjk1T"A ")B眵!4ʹNgk 1 evFc# 2?\3gΌ>7?7Ei_#2[n~;xǛn!q3m;G_"HMQ7H؆$AIةs|MRc:;nj4'A1PݨU/LN4ZyOL \70U/vWzVՊA(!:*_BHDni*URccΤ=9Ծo`<k cS==cΥ)A0(``)$;BTTI&JiGR#տ ;\#RTU=,R!"X{E)iV*-5hS; =HT"CХiud}g}OJ#ۮ޳~ۖj5qV?s δOy !:kZӎZZ;tޚmlPQ5@5>H|vםtWR w)uN<ٮ(R5ls mN+87B,Eٺr$@w*xU.P7bJ`M% }U.{*= fɶL琑81ܴAÃ7/{G0 zd\~xh] FQ(BW=e`,(ȳSi(0WB*MNU-l]lL->{G~iSn|[7oھ붃b$BJd|+$e(!VEXyl=|FIOOo׵{0TitFf{Hc -[صpE6 qF)d:O6BCl ZqA@;If[lk Vgp`P)o~~ bReUg1R!|bnvܮt}KSs9b۶rY ,X&[EHl2Jk!8f BAiRtjDeBN7j(D@B7\l%S]daZ/G@:xBb2,K㰒P!bfjs{۾xӟm&翸c:M7,P*|UwDf@~_eϖv-o`a4fs:ԏ%{0;|ϖroϵزP0IkLWWi4Y\Ráؘg9S28znس'Ɍ]b2> O{q(sAwyy[>,/ȇ2J5}1tRм*$O[7;Lq[v8@y嫹|'1k )*t\eږY() .Ue Wr J8[9೦d%%2 IBRteMۮ<{du;bnj~.q$զf~ c\A{"4[,M(mxav*$q&TY۴/r64ʗF6V,3@Ʃ3ǏG+bԩO<5~3ΤZ 0MGp4uts y1T H%@-_OХ&~l,y'׾BT.04՚X-g*b)"r33fm\tJBR66RtqRoUP03bZ,Pqf ڟB 0Wf.BzLh.5({~R#v$ҌLFBJjvnI"\5J%uYB HJ X2P^޲.г ۩\j饼>WĿ!6=9Bt#/eIv;s玞=y_/7r4^|"u/zw~mcן?ֳfS"U$W*ѹqOBy/?JI~XVRqiՅ$jud(ڭuV+:sx V9NPOl4ۋo8lj!@:r}4Mz/ם^a bsB.0LLL^^1lS[z˞,"Q3i+̯k*|BuB@vC?x1sB¤Yd6T^WAlF,;e=Fp/PJRu'4 vni7|^8n仍}a6jzT*z3>cJmdh] 笳7o`F~a|oppf:&"b6֐! WNhqS.[s5,Pʇ2GI IA\aazle |odzL>r&˒$Aā>SszQhZ5F+/ЁKluf.DfKD sz!MZ"pNk!P :뜧<`*W_R>*壾;k /BRvDOlJ"7>ȒwU05- %8*HY@&9)26RjChbf@9JyGN+BK+% `t {>C+z>gOdW€T64h+ (:1:_(m [tvzlGaP;=R1s󣣻vBUʽSdyv6~bC(pAu ΍G~ridך؅~GZc|g[u Çwc?́Ƿm߱cAQ COw^A@Z^$W-1 t}_̗Ϟm,b𚋋ㆾ/_w]i.s=&u "n42`@T-n&n¼HrP.9댵l y99Rlqj9}L J z˕qOm۶P[X鉢J<}j\I/Z=3;VӁHmàD:rbF5t֯FN|Φ(_dU _gm3.8~1?8vk7 @',5Ip̥BI)~o̟KK@w}Q[X`,E9/)/t;*z~84~w 5Ӭ90hL|I<͵HDع:879WRfi ց@\g 4P(m]P<Zv ` rw-wiϞݏ=?;R$@ _$]0 /p]i !lB?_t~4> =z_i]-۶lEad!"/]@|vY#dG4|+ < Dc Ub2aL+TҸ ]}%ÛcӋ7-V@r3Y+=^9=::>qӆu+]]@+ƍ[Ņupj㺝IZo4X y Q/N80z>VO? :r&g98 s_"S c'߿~G׏{G oqdiskGi 2J(WK@j`Ff%!5Mp{_x' 1ct`-Fs}g;(l srܢsT(ȀdX,R+##zmfj1R;B/zt4-[WSX͜O{ݹ("v nن^M&I& bF&mh6eH$"fk #H!R8I% !SR}^b^s6+GR7+Xwղy[sF߄+8Gd3tŐP$Q*Q hV!wH}ؽ_W#~{Z&kԀnhґ  ex8x ~W+d]X:t8BhT*spxdcF:mC`( :g3 dR))֙% JR"#ꤨ @E,N0 yǻZq<{wۣJ8Vs% W\B+1΁̂XH؀o=?L\o28SUٵ=}ic: VIaM+;IZ oĵ{2Py) t < P <ɲftIAwMVJr BuB`u{6 gFJũ|s:ʲlse(J333֤RnBpjS6 rCI @ [ql!g"EJL;L{gM~C;k5f!aGp'eOMc[SPړ9l⾭R32sg/f6ne #9AޓJFlf.+gi+!BP4\yS@ Vt ;^vqѪNfrwqȽoO5{eZrͅ TdB0Ԩ- Qg ,Vt7N2;3mwF,8Ǣwp}C9;C瞞CH0FD,dbk/)EaX(Ӧi1z[ק&i>6 )7\w睋J@o-$y$8(! TiDv7F,A|;o 7kW@*3d_ܗʅbw؝AS(e(W*E vjF;nǩ>9lvvJ*]q &ZKKժI%}sT Cx&3 K( KHҴZ;ގad0YIsD`ㅎE#R"214RwP)}@M {Y <Htl-^{ot,@d`Ό5#j#rW@O=ɱ)›oUܖ+ˊ nœ{AѾ%VkL騧/M?~bzf&]v-(Š\M[o}ܑs~f2-e#<BiL\uɓ']w͑|#,tB앹"h, uqxez!@o?|Ƶ&eV Pwȵ?~ou1Y~]!~,U@ k|Vj-H8ر%^M!Ptj~g _|ؖ`}aXOԙ/O*%\;R}5;5}99_x~n.V6֢=zunKE~Pqrdn) ұ3'm(ț$4>w_<$Yk7|CJ) N.HA6dĈ9A#ݨ+E% ml qSs$7|桧2V/=Y,7Z!ϛ9 RP9NRi{Uoڷu[w1R@zӏM̲ ‚1XRɑ)O+kYR Q*w,mLZ E9fR =tK)%@Le9pbMM̂ @\wEo,ܾi$?ٲsO|'_X*FX9H0(4W(xoRRG=?VWۿnaBX$I OMN^+??ioff >wn2TvkI@ىӝh ֽZ兕D\|OѓcչE B\w\upCzM֑qdTivcNS T]kIp yG ĦiK@Zf;ud[6"xII;" ֤Θs'7Lkm~(i P8tuATb==}B;^݋Nhsڙ3i6z|Y._xCΟ?("'1$N ±ٶam7.,TfKXgki|f!1$IJ,T̴Mf5#:H=tkxhyOWrЁ]{v ذy.2)k7޺?c JĞg١j"#+Ϧ_a1 $Jng2TʗN(@,#%TN{|7̧F$J!_^yjt8`OD XB {[;ox{{shT(}3ڰ~ݛr7$Ivկ}P(|~/OLTMO, ouH RH?U5TvnۙٱxT_OؘDA\w#GE$,8`$(/|Zx:C{"s"1.lj!5/V(J1aEL`Vk_ *XFV^WZZ5>+vlv{#]0}nlqiiWh Q= 78v?4WFkljS&k#d0kմ`C6M DG ܻ}W9g xf3Sj~'kcBud c64*28(I &i&Kͧz{+oF/eNǴA0S/F@ԓK8B3cN?|4'/O m޹/kV e@JM5N3c, AÀIjsȌB$֊xIM PM傏/Y3C6K 7j@-|kA-eHa1B@T88x{ @=TH!ivHWhI@W-P^+QjY40vzs'^{+K5oܿ=pmr碩k6&u Pʲ0pQ @)@ #$uE+K_b&LyI֒*ڱ{G-wWJ 3s'?x; :􁸺TUDiBPJhg3#CCe[ V#^j--έIlkvv…xaZyCe%t+t7\/\t>B`vfQOK4qUkٹщ x^+NYJ3*,j[bA4s"ɰXs 4D厔w|pS<1˙*]Yc',uoݼnF<) #:Bi 4t#R*3$ 1/ih 舸cO0K6yMb:wvD(4!ڤXd,` *ju:=$N<հxI~DZ_,'T3vmBʍZHN8)ep?ݷs>wɱgΞJ<J@Ǐ={\wuW0Kb ihVzf&:'?TĹS'&ΞMMt#sBB- B"p B-#_s..h)KKLr (]a_\!J`yF+)t>;z؉J#G&<陝'VXfv[JJ-W*Qumm]?@2OȖ;,Z A*ZDqoi Q2J6Az-=OK1aE/)? ;qQ3,RdKw\8\ ( H-?5K (t } ~!pm7t-M/&s=280cbwE},.7ɗ5/FjDq_KL^RH;<@`) \8D2e[P/V5Ls˞-?3?m>uv|TJL䩓{v]zbȪ W>_$(]gDŹbNO}Z+A$f{:6n,e[ᆬk}5cv`^cԀ/}A.f^W)%rCxiu͗kvڭq׳~-?=vAB! )[pXuazfsg26oںtw{I#cccQ՗'N|A$+J*lkBf% ⥥8y;{zɴ%Δ-2k/VlBclۥ|yJ T.Ƥ+qعh8}gyZaf[w<:vRupXPaВA%A F$J)^߲sTWT5ef1&r,kvT;EL2"&PR̶vX sYKxfhd7 ٔ+MTZPQ|]kL:  ad0X7mۻeaf~[=vY"GǏԽ4޼~dݖ\^1 "pe^X˲sF*i?zd݁'0mUAPR\C5Rnd5tGLh }r bettM:e&mO՟k,# F?i\* Sv;R2$Fbrl;~Lġ5BD)~]?o޸ȳgD{qr+MK^vkK}ѧgg0ۼy#d]ׯ;sz *n K닾I\X.-[ }Rm'ٌ_5 $فqL w>'<E0?=y/jV]8wӓ^u{?Q8R$ !:B*b R_õ%"2" $ bڶj= 'I 49*|E8(xes9z|_Ӱ$:(]ed U'zO<3O,-,*zٿ3ݕFo} JWk Wp챲"VY@B}>ͯ ?If>(!ܴ}ˇN[6A- ȯp\@Oz! yB MMos1?rIM{ZcI ғ^Qu`8vZ_ Dk@EJ0FJ .KIX_ !7G޿c i\D 8D 0 (:C Fs51W?t҂:tsSi;c?>7=y\s]55"[ O!v("$0h- Bܼ/5ZfvmR=!{65–݃[.SOP>}8Z/2P]!ȽC?“3AbD$ILb|x\}WRe4D*;m!`Kg:q1v`cQaZSgN]C&OO>SOJ!o͵|߱k_,&h]ϵZU f#/&Kb1?;^dx}ܙ K5C %ʙţJ(Bk=y=l*6 0_޾u.A$AZC}zݒYHro{ҼsQEKvL1R0674HYQ5L;{%IZk0 sx&%"vd̬F EH i2ˎ2YG1s1_ImV[X:yDwowVG!C8uݹbn)E+e9|W/;hS-pVKkom5~Gӿ,qˆFc#I%7/|ٳ7 _Cn'YT$kjEwA)LjTJjTۏ=M[N8[q̹|.U}OedPA?{wžb,M#Z E#ASSO6Y" Q݊PH%qe34@0 Q3mܸx=DueKSZL&Mj~@X,Vnv"'=DyvŲy@ " `˕(e$$d"d)jǎ>~Tmზ B@[G1i )1@+)%^:g(غ~8MJ7֫ծJy=RT.*!k.w3 !AfJ3s޼+'=zbΒ㴍5J>d#KftJn` <+J֘Ԥ^kTI)T+vK%rR(O)J(tcr01)}7nKidO}z԰t.??^ncSq`sKD,[~~tV; "@pz"g Gڭɓi,.Y =A"uuP7-RI\r^XRN"@&Nt~~P(b&TѤFJ)P#CM&9"VUklqwZj&ptX @Zj&r䈬Fr|n6UWxχ/p=ōF`[RA1 3Bk )cgO84?zrmZzahmx*1H @ݴKg\gi7 g+=" PT)i-[ I ƭ##CJf_/8֑=EHPEh;rc3A15J)7oFQe#kB*%ph*%Ґ]2d+>ѳNv  x&/L}7zXK6H'i# @#h"BH; ׬LiwۿRw.+߷u ΄q_˔.vNP@ZSq5}|TSB P9!o}{|?|zNw?W]m|'(^,UWY Ე^$o^inf~M_e&VX^7XO{?7]}rЎuS9ȌSY\mU+1b^duF0[c:nvD/KSgMX @GjKt@DLӬP(y*鹩!c똔2#^ ^ e-oq6K~;>x'%m b#! kqqBBkRJ&rJ+)0Ro^7ؗܶ"<VJlb!A Ԅ`ZU.5م\cʅbn7 \/!^@(_E^d%iQ*߇[l_~8iL$0 ~'?hݨ66mGѓrQm6̉3ƚ۶Ehiq36m={qd~p8HX 3OK!ȋb Rf2/R]su=׮1#cyoЊy|5Ed|u]۵ȵ=e (oΝ{KKs宮5^!vRh,8Y!dX**@ׅr.7q$iGS9+A*IzJȲ,<۷-ccgQ~wM ?pwU@vXP(P8 (W4j4ZZ+-eHCvwrpOP9R >s uAxsSS(Fb{XpVhlhOoƟjUwm޼o^}֭;}O>%AUJ]ܩSژn]woyx;~ g>v|[z 5чk*=]FonZSbITB! #8^@G~B eAD!˙J]4*fY&PZ/ݲ`[G?7_*}G?aæZ>:1١H)ifu!Ėn ˥w޼mBQPS2~y뇬˜ʽ4{ywreZm\.RY0RXJ )$iZ]]%kh9b"FBBǎmu޴ G<]Xp#GDAr1_] If CkMѾ]Du^i6Do_t̳K q.|h@J !R8)X Haj'cD3SYcBpgZ25;3==>>>~beYVE!%!k@ ؈Qo2PdARy] SB Bdf-:Ë1W 37Atk6f9> ,OZ 5F>ʭx~ W@ =>?ى_7z;w0qanf3s4yi|駟}X,wıcJZ۹}3g?KD c}VHOH46%$]j ʹ̦-;2H)dRy%uzQnN[wMN49z^a?53=(09%q4"|OveR2sF;J;w=mX⬻Kn뺬@` (̀P@*Б ؈Y@α;z~:117S&MNX عmAnㆫ(bT7/.*v3Kf;IlُL+A.3j (D"$b6i[kmK ק~4{]a(&Z9* d %"mIkńm"/ߘFz΁]ҟ>֓^B)m j (r={߽]n aPG9?JB="tB"ⲘkOM(lDzf/A DuC쀾!P)A\]?,#fDQKrO͹O}O'#TcPw2g3/7o&q6~g?x;yGϜ=󶷿=Y/W*A1J #aRMqNϲef'*`8iq48N,EJlYCMqbn-֓^Y̞`}?F(WDY֨'#뇞1V|,ǪmL^?8\|^eۅF RK4ZPZ3#IDL؁yh ~}$vl}ؙ?.F PRi?`J3|m{J513&vP$ $ lٹ Hw,tw*^Z8N$B\ڿymvK\&q*Zc qRxR!qf(/xݲe'*d%` ^\JNiBQ!2%!V1P8{C؏]}?߭.R0f`]"{pgz蹉o}͢\?v~ǮG}oם>qa*:n PG?Ö 0 5GO ׯo{㖭[|Zo>":x`Fw}/w5M&ƧfV7 +)bLM+g@(P 㜱 ]wW9tnSa;m9o1 V~xE c[hQ}){ ;P(<)dМfIT ,jR<%Yָم7 l%4 "="ւ5(LW~v+##O?u [fk,dXvpCs∸$ t" P>WݼBWBvw}OZFB85$Wxq3{759\:-]ȋ>=?ؕeV ~p lCB@y3%gV <+b X+=l'm%?q;޷g߃?4.V"z`f3`(XfT'Bk'Bhg9x37yn0ӧR ~aV5O?OhPs3 )3Ry!K-8OK29f>xHew,կqSCFD@*= rUNƋڻ}Z=/ T'x;~fh`pv~:1P.52Ƥi6۲ek^2377',Iqe+޳ML_O׺nՉ0*(tN)JLUF`ko{ Q)$bf,0Iҋ4_:ie !,eBJ,z{xBh)8n{GZ[8Wysc }Ӊw|P]#r `覫 9CTH.JĩPJiR);@t,ɂmF@(O ?}5ZewnȑSv߸i?/֛Fc>qM71e`@BA,P 2#3i v筃  V 9g3)ĥxeŪ ҿֈ- ºuȑ@ 䄒8Gۋ Ka*^D%vҿT) FzE_"2_еr.=TPt$': hEi1^v5QbA{I=om]s:,~Ol`ppC\PBͥ&GQd5IAf)PtUZx̌@fc2 !Ď;v659Ty^?X[j@]ҁ?S?10<#f[zR}֪sJIH% H)匃3?EnEa:^, _$}dl)W{]oE&R\Kڠ|KlK +alڌ"%=rn%a~XkX\Z*Q{aНe<6>QQj0 &׀,k?c&$騧{\ڽ |)3ps>}ԩ8myӍ<;9yfz5'vڽ{rQܬ+)\[i Vl'@Ee@",SlSvBߖmyU&?t7:O'=s76l9zboES  ;g,piʆr!XA*\^ Dk_P"Z g,.,uw * g3'ћ\']tGٻo?)-T~ ʣh2EkuHcƙ)A $z ℆ۂ@fL1PZвrK,e?/M{ѥ‹ILkZ F7[;۹1RB~ ?RimWYkfr痞~)gݮ;~#=[vWgΜIthh8Ldf,39֞=g-"J!;ioPR>wRYkdk;/mڴIJ-^#[m=tu7@Wy%Xݽ~ʆ֎YzzIIYM._ ^4IqNW"fWDJ)uJңti`!ປoL糣ϞƜ 4kK|XrQbX59Ig|+YJA 1T*'&&6o54!'ss رa{^}smT)3gfsyvp~w-gΜ9y[oB.%L$Y&=g-1(ٱH >%!-XʽLˋ\/9(XJhD1bh3,>c{+?qpKۺ0ض{tt\\7?:(Q8P,A\p{џթ.bO!_8KV1[ 9/_f3cAMfbF;2FOwog||?O\}U7t].!Db`ZZT /,;4y=h7I+}<(˴b^|RldXVPȗNc|QI{^MEWj)#k!b) {JAǼw?z*[֫sQյ k=䓈b׮]|pbb[or_Wj[t]47VOWW7to}>w7ƭ[K2 mͤᇁ25Lix ExM]KZkJLk%Uge0I⸙9yꉇLXցG(8K\R);"G84F,7-Wv2kD'iV01-715UM[v̞`xh>Gyw9# Y :Fe[Lz: ʃR{H%/ ȱ1$gmrIbzzlջrl:~J驹ΌOjѥ1?'?HMZ'gV#Ų<]fifzE;˝DL"$/y_T6@mnv&Ph)ojO=9Oɳ>w(e+Eʜ˕ˎ{oܺikrjik0X  AǞAI*ѥ?_*>c ̄,:ayv8ʹ:Gu[-s-'m2;10lQR+{n8qVueÚZZJsIȄ@ ;ՂydWjM-I W8-Blq.f8儀&~@aZL eS@@7@Ԅ61x!;qް-N"[(*oIqRjܰRNdu%YC*f$8RXZ/֔NHHgd!S1ΐbBJedx͞'#0Ez8 :h!8J(EȪ)^j?l|#,MLJp)A:߈֖e\br<|ptU#C'oڷM/q`o`?w#P !:14-eXӂ t%& 92~e%-"Q 2,`;I޺jUOEˑcGO-щZ#&&GfyOSm&.ё( ]eg4YsyZ\jOsЖI :Dpb(.s9Zlxd?ÇSp|5,[mƫ׬Wf|r/9_Ji(L&כrqק׬Vg³|]=+BS &\yBH[E6jEGsRuA 5K6hQV.gqy[Z3F:Pi ),LHZli8!RKЦbtc])z8ܱ@kD93ΘTʪ*Ƹ(Lx-JҪVЙ^+v+,,$ZJՙ/KUӌ5]n/P KCsjrdqKqZ9FDhq:;8zĭ 1ζz= xZfKB[PfsX-/dzlbj?Qk4f+X9 ]3pdQ)}fVfNYEl"4(@-ڛL0Gz:\a5ڳā#5 Vx\yF deM8"dFf߆1jhScLIqFRȶRG8qx諻Ϝ86Ltt 5zKo_245,H3ps" ӈX9rTo>&ɘRT`|G"r0`9sEKtkd9R){G@!uF)2 PŋWn PmmtvYTK SSNk 0.ҍDִ]W3ckF#4XG8 0ƍVMu:]JRkM8g6S3.9IMl1=(726-!̂USJY(Ut2Ѽb@A3岱^ Vztb\ga43=c$֨6}  b#I*M>֒ܤAuw\7\]_G ;y˶9(JFɹ(_Q t)'8&M\u]7Fq5%RʤaI H4^& ۷mD nJ={f+bK˩S#azIA ͥ[S7w?YE' W= &Q>#,tl eFJ%E<El~Z&=hgȌXVpDelLSrKi *MCJMJh#\DԝA ĻEm fj|爗_'7/s_nFqD4 qdCO]BJH8Af iZ0T]|!9"RZ(y&ZEF;Wlp=\~f#oJ鑙FOO|5(WYpQkGfr9 )[J|-%S  tO=RmZ.Yu$8[|pߞUiZ,GGGo!w{U꾟T!J-&w;8c|jRB:;2=GVͷ싓F%b/*/=|L4 2&۝hq.E4F ^9R@btrZT[<, \_R/\uk}5Eեg7]q9rjx9r,.E.j-;9?QY [pP߹^>'>ݼusw_ "goܮ o,ٹl0<|6m^τk,YzUȚ&dmuQe+L.-rT/[(Tƚ\6[( d"Imtbx=̌L5c9uh&/ NV  n&Sn{$61>k瞙{۹kO'}} M\! JRdEDCc+pNO?|5I2Fk׮JR猐MƑfl&2eLkPiMR֔J)NI[_Uyj!4qȪX)S; pFZY RSBa2d@ĎqΌ6FɁ28By43l̅.{˻xχEX5`$KIj{\ Erj6hsDdI.GZKQreF%A(a-lFI?bؿ}C'Ƅ3S޷߳媍UJFMRJc |~K-;ρ-r4^K"=FL$dQ%(ɉ,5gLK "cpb|CWnyL4MV=sX˘@J\6]GGgTcN>es  C^]+sR M4f!8 liqЍ@Aȵx@Ff%g翻b#  Uje/s/Tz/_UGGGwwϋ/EaK[kHh C&=43F@)75d-7 MWX698yEwo\e  i@ \JGh ֒e2G)_Ixb~蹡I- zÆ5{m۷_U f(@0IS!8?Z]xmJ*頂yr`}Ũz!)!х#]Fy%׫D&#ԫ!s j8^Ĥ!KZI"/OU$(ŔaLƛoT'*S1f 2_@?+c,[9Ɋ88I6{^V2IaZճ,قt\y>ͻHNS7vt(eԌ~03D.O{ nF%i)EL(O:R0d$ERFD! KYk͒1{?έWoQZk\7M̃;>@ =aBTEݝ=zkv<'ff 5D[=ttt)(uS>r|,:R1-dFyvБhznd 3p$&ML/ d9Y:'` ID H  ˺oC![s\> m7n?pG{G3]|yPx,:q(xF*%F'Lk=t%_,dƻfeZ#/gloE)teh :M2?+dX Tp!`F e.RD4YJ HBʦO#N8:5s7$cM椬IAٺ,nE!M)]=IsqRT}?뻙F.\W wF^ajCq’U3td4OO@GOoc=_4nb͊RK)թ6FK^&x`Ep|2X1SJGy!e6ڵ7n}YI*Jk&]C\`+8BzܛMjZi$0`R C#d[8D"&1 &k g{Ro+6Fhk)u>zh!NS'sĠ:0W^ys!Wg! B΅9T =ω2:lFlܞmj˕];wv7z-Pp9@Zsw Z/p&Iu\:^>Iˌsn[Ckׯ\>3>ojr;_w}rq?Dk+elj f&γLJ3{OL qOڵsG&]{ִ%\\"B(80u᮸J Frs]@SYc9HGgGOOWzhhٹڳOHjCp),2dІ:7,LIxӮH`r!՚祉j*m@ƪg64s1kZJiA.—$N9\ulOO1ƥpL!A+g+A>_Y+kDqz*'G Hl4:3ܶғYe0 +{]mo ZdLG/6,.Ⱦ*+ & cLpaAd*M];J~5ςJ䵙ڟ_>ge"CJN}'?|J$d+m`A|. Bhb7t 7{^d#q?$RIJ+H NVFOM4+q$R}ω2m,2Vk}g`Mݓ4 Pl6g\.YVgjyF7QhldD|]OkI1|UWүou8֤Ms]{Rf8w]IV514z `Vw}9(7cmmi\92gҷ}_>w v`21%[*A<:Yyژā֢k]N 9BLF+ss3Il۴RJ`֬[rJ@RQ۔+Hi\ޖ]'\ h*!6sA”?hJiq!&s#Ƶґ$,_ttz$Hdsy֯3Z{'XJiF7 P2- XDB(QyV^op!1ƘV:lgVqJhrf*3vnreW^ْ[j&]hYcR֒d?Gju]!$g\ ]nDԮ.ьq 2o b|namZ+9q2s-, 7 !!wCPDQhQNAEdBy~Eqkτ`D7V 2`L, n7^;Jʂ1x}1d΢Vy;r J'Wmu\?jg ظexK491=19s0aSY\4棛5;5<7>] FGQ0l*^fŪ]Y\{Wm8s7 kH4smk pvL<s8t(Tx' m@N c(Ts&ؾe=⎁q1&Q$Ǒ*J[k"6&C@4IP%gRHkM,JWjBJxb(LF:" JI+ueHQpݢB314Ə6~g~;dqGXDI("fK#ffvvve>=ץd"^Bb3bM~W6 =׀@AnQr'.'ɦpEa~*IzRS$!ٹ#?{mW4D__7&,X{9ߺcMYKi2-Z6oܨU]R.[b0jc-l.GVTYKҪR #FV p@K2 FQd*1$"Xe-)HJ!"das--m͛7k|H7^d Z .lsųB/4:4)Xlgۚ&s+aB$3=`ZahcRlia daj0t=+7f%}fIsW4"?77Š*y)#ASurA=AC(9uzb;!fի֬dɱeQIGKbBƤpK|߾'O};޺|ŠVVqI9묗&2lRgkE#Zl6j{v_Xv5bXwFI}mGi!HуxOeTkFY~G];ET9 DbI3^VbJ3ۯ:WE/ _aH×tE kq4YDTZ)߶y8~źMGrmҁF#|?$B9;u깎'_Ա#NZ+HqFX$MRŭe4-X$ g6F6MϺBJy-?nn|gi`s sDžlS^jKI#XC-00 zW/{@#Abղz~T[;j-N&89!9 jXຎ1hŌ=nFBF ȦL_Bj9CZ1hıç|bgA`Jzm<7ֿz,ݼֲokq;OZdV*B"x,afQk^I Cem>ts|mLm~/&*86QPu]m)/ӘozcFC҉c L:s6t3 T4DT]+c.iP%pS*Ƽn  IHyY;k<] ~ĝyl8v^k_ʒRC~_xɉɉ_=ot[ؔJcuOcɢh\=/yHiMD֐&$$8~_{`zzk7Lt@0%kr6^nttgclE:e $^n[ʄӾIRGhDg}q* !bhx4 |ޏ1fRgkI}W"V:"8hL8%G擤^5<_" "<<97_שˊ֬h?u|zܡCja8c#433M $G3 $|rHD8#RDą?1S!B P]{䷾7Ji%@$I7Ɠ ~˸6[<BKp|dWa=NﻙL1N\W2K=/9 T)ki)Q8>>Y( M \gwWyrP+ kP~j(bĀ1bi:k+Z2thZzOӽ~$-ykJRGHBTQ | AT ]tgjgHUZr{ZM}#{~ a '&&f 84U /\`!q\etyp7]hQ"Rm6g0ף:dYPNMLj9c2 pq͙j 2q@V%A3mWl,+e ܺz+u!(u/;@sU/{7/}F|ւ5 %OYl8~Vg>;1>KY(d8Q&N )9$U c Fă~DۄX{ g _R/AK6ALA;o>?S<6!F4B47S~">WGsT˗99ep$L[[iRT+ƙl6@ w]nu8JSm1[;Wk \uGr+צ[JݱeJyf3$Z5N&Q&wC]~-WoXOK:;fjոQ&mR>j\֩) {y^5կ|ӟ S,V'n|uRHD@#fmB @ '.@=1邟( RAa,i#]cMoFTG%K$f82NV,JN|gzNd qΛk5P(ou8J8rMo=pPDPj)qgg 慟qlHÎ6eب_yu}u%8ux`A MQDS P`4Y !@ lq`y~k^ΒNwkζ^D֠<9k r-_ڂ/K# Co}{}K_RZq̥#$AK ɒ@*u#Bir)-Y Eyđ}3FJxe#Ңi. Z<9Dᇞ(ϕo$VH9YmUW=h"B!rdu\?296U=G l!$$DJݯ#-@ d L#O:uno|}$IT&r .G7l퍷 D#ڟyDțޞFX2j &7'PN['ij-[Yk) BkK"im"ZWdE>W Qm>rulllvv aY ˱9ݱ>q{O+7599562&q?uolʽ;_XU7ly\)߰[oY<^9 53MCMP :uY@ṫ~GZ[ݼf$I$mkm;t$c/K*M8 b9X%5ggH@# n'?ζ?ӿq[1S)[b\JQg:yRKVR[~~V; #;~jZJٽ2USSq&IxB>+7(c[W=Ed,GqaD q+TƦln4Q|$BTڨUla>hk-PzXk 2D=~ziWNc9 eW<$,TLsK`xm R cJ)8AVTz ,H71뒥WÛ#.51ZO."R ,q˗#ؔBcG4yRK3F@1JHn{OTi%:2fH%&rzxS ß459ߏh8bV*ڡçE1}ͪ-m6/JjA#sd1qk{0:J]cܿ)ZKd,"4;.uqS&NT*Ўg׿}G>㹁ή#9~kil{ǧgfr=w308h RG@fjQ8.gόU/Nig09( `ơVj4_~^ :/ӑ/}@\ N`ZLʐ )*=ݷc='g%mi֪sc_=?__o)uk9G C6L:@ON t qxСN Dww5굃G2u[ۻ<$K-/LE'k٬(1*;3]Q@ 7$vOI8rIJW%g-_١m& Θ%K}?1i1ZmVDIl;*a8>2ԣ߾zՊ$ }3^z\!S. ͣSc-ܩ/u2΋DžVizV=}2"m2+I[)9FNON}ݳkgIPj~tæ/3l\verdc `BQZsFf\1)I #7+rW:y7PuMߗUY_t'M$MϿhh3Dk#r]40%JBZ=ʵ=7f3zPh,Tk WLf9< @тykoOtÖ|K_p*ؼat3nXf?}C{1i\%"F@ܳwOP~wC #(M: 'nR,DsMJl{#)H1 @]yd {~/#.3IJ)r>i9!0#2}[9_>1QI wRkezfa|oCֽ[>3K5,TuMGgQWVS1``bf.I]/i  SH'+Zsβ}JZ'6DzGgQ `Y6W8z;v$`ɠ+60% MiA2SXۤP }?R;HzsrNO'GG/q-=~>ͅt/}JILLRuRB&C13JR="ΊarloW3k֬]i90$1Qk-_/''m a7ݺmۯ<94tKhci{kٹ4PjR5 ś)Ӓ' d2`L5itܐ±NNwx`-I0acs|y;Xl.Gd<]Xhl'B8tz1ٞ/d3B,o##87Ґ8(;K-o떛n--yT4!  =lw}/2$l[$ںm7~E_x5~>yׯ_o:4F ^C2Wpku~Wk]]J7Tʠ` jiooB`R\XiFDh-\&pw>܁"K`?ɐbYM\ 8gqԄ>19! Lx\0gUSlƷYF$N\/ pr绾*ٹ@agW{{rkmqϔ(U*V3OjM\8F;ƨ$,GFԒE9XHր@@ۯuoiuv/m&aNvpoZ0/ܕ+OO4<׿0Ժxu^yJ>7[k4jG{.-MB u2l}0)~eܫ= ;n޴qu_xjwvtU}(H)Nd_"Ż27Wl[kޕrmma2!sH}5ZQ1 L+Ǐ(.vt-o$=ޒ1Ht us~؉f6qAw8}?YwJ*#G'!Ga`( qĉRKgL!WjkyO ocG~G?S_NXt$ hժUWmџɎXԩEU³'y( ],z6@M#m$lL*.CD 0pƹȀ#sŤϘ`3!oF[{Ԭ0|;}t!Ok$LF|VV?30&MqkQ ,jD^Nh)4_Wfdb=F8z:n\bK[QXc5+㻢Q %yuNb&[g+ ecH_0.R\040sXh(j8g+(]P-"# r!"g=S}Y674|tpEEws((.If2ѩw=g붵Ec k;q.wkF5#lzo,p.0IpO. _X*_H2dL `$ۮo}۳O6ͫU"@j2^r|2W'tJ㻎ȗ<@ƚZM цqcu|ajEGw=o@֕txu<鰦P$F3) ךw<~z* m68l1Bֻ}]fGa] AO?c#FȺ8,L ya 3 d![eAi]HHS#L .FgWyKֳ-fehٝ)}Ak-RC'͕|Vk# `@l"[׿FOJmJ}&iϩGѓSC'3z0>;vNh ø;I$&d C&еZu8V ԫJ(\K]sMu3Mg\ҵzmqڂV<˗Jˌ7Гd$Ⓗv8 .y ZmLR.OUʙ$P@%֔=lpYqFjQtpV='-%t(j8.r1DWM_j|7VVIf5X+TJ5ܫ# Io|Rn[2LGTםd`׽\.O %HJ~>'IZkMJJAGW̰ko񟜒HTDw!6.8`  3rgӖ cCl֠UL= 1E.%-$eC ,tbk WhئE4LP:IǵAfsyB-^n3$"a(mZyԫ:jA65dZ `?y⪭=c˻[ێ<ñRSaY3n񖵛6~o?3tzdX}kη/V[>xOumbgwS%g/IOo102 s fnS=+Wwo *dnKl٪YASC'S=ii ˛ٓk/u-)xqsuMԢ-" kimXJWkBXJIZ1ƤB)e`M1UERO[ ZH-hm-Koh۲m(b~E0h8x)"c.V5fg+l5hC}\1.:aZKDJ-G-k9x -MoXaq8j_Zl B4+V 0 g}$ Nm(?92#Aƚ&RZҕR+jN Ր/|}YtI3CZ1CNLjZC }-Vٷ}mo[3NdwAЪ֕, !8\1$" qBrº^ַwR#$s/ bYƥ@s3`ysG:Q XrҮT YI8=57=9jnkko+vq]2%ZX2@g`ԉS_7ݶ^gl~Rmz=wMOO%͏GGW^Xqԑ!ս;ߐ3V5#~n /9JY~Z "dQ'*EΤX  %:Nz ' +:oܰ d5+?{OMĚRcaf3 lB^{=7zܡ_7.0ty#, &g&3~FJit~6˥S.$El2{B>yѺ:_s2+|,q$%Y^*OXyL Xep} [#iTL/MW.92i!cҌxhȐ(b,sݬMɉJufjf5nT ZKEI WʾDRE)(2.lc ;K}] ![Q7J%,Ĭd\hDZ ]J炟/'QMX(n}Ӎ:sS 5u!DHjE`mUή|1_lmU[V5TNM0cc p\tR4d@@!h"ؤn3JnkT5(*Q:Iٖᓓص{_yB.T뼴"""cIZR.:;JJ8716z]{%a2dqQ(M֨իְΎ֖RP>?03'XW`|&Um]rR+*{fg0NLk#tY ]o_LL~Omo֞ Xh|ʩ\~WV01H@"Jo~[x#*OOFE/ȏ 7ǎg?sGy)VO>HGGSO=)lkmEoxltMrȍJ k. K /$c rά53` tI',eB=?r>36YO a\Ӛs XevT;r𑣩Fql[XKWGs9R*Ep J+$)!y;VQ)IȒ 8JHpC#'Y"i)cX̔Hw Q OljRٜ afsZ9/gL$zzz '$zltRo``Ȅ|9j1X2WnxK?<lucQ=V*݃  Ρ|oGR& ծ]-KS8?GiC [|su4*?o}4Ubȕ֩Ih)7BGM 2\9KDKKP@ /YNM$ `Aޯ9v|cƘl6ӈk1 9DK:%q'ѣ9EZVXpXjw>T}ֶ{<#Nvtvw M܉#GoyM>hB!(sk]#B+N`^X>\yv< y]om|u7^k)`_n;?#%:k(|e~ժU> 1D5֦QɔZl/F `ac@t9ewX|gM[刌Thُ|g3;RO.G:IC8N&Nù4=si}vSGloL1_k6np]7#%@qVc'5K.:q,LW&\\!Kha<2tG2ڨ83wD󖶶lSK4d3$|Z[ZDZDMTEGZ)E?BXŦ&cBJ-0ǧK=;[Cw}A h`2H$}gwv_özP ZX=x!5|%:pt_odx.dD+8ZZו]5>\u]6MfǏ7WnwбN}os^!ʀxS6UyKŧg@\;Kذ/C ~Mcj%)Ftxu7obú(Egvff`Yܬv̕4MSzK E&(c#2:[>~bW_oGGG" CXz:*όOOϦAcD-)20]`tm~>U驑LQٵ]XP\ ~׃rLwVxJL{笍%s{~^{׋7nY|WޝΖz#fbzJXPo4vwvx'ȓ?=yCY W~N?~ءZvw=aʚ 8di˿Z"Z8 A2_57Z3p$:bB` H}|@t<Ήٲ `-y_`\&f-^xrסG;;;gffLF+eٻfJ6VO@0JsdH 4TNdScȒ&D⸭cK@cRkY2B8d-ƚ\6JW$I8vz\GGbrjJ)ʹuGa:7SA-O޿脱VYPtSUs3}eX~)" Q WP{|{nΎz9~21&TfB{K|mƀTW3ΆIZRzZƴ‹"/KMoJDO獍}b;0M[OI3tLS|3T볕Fg-=zCWy@c,u˺DŽ`lokIHJYupƸVw, ܱĔMҸ:_p٩;{ږ)5=oݎz6R `]:_. `X×giJi0GήT 9\sωHM/6A(u?4|xϱ\mٲe3'}=] /_OoyQte]x#@HC T(11@",Ju0MH25,H=YXHMJKej.^¹G:1%p:[.̑'Ib\kZĮ\.6_SۏEQFNƞG]mA!c#3cި z`!lǝ\k"3z䩓.Rц,[rUVA U|bx*[6o3cGOݻs/F U h,Xc ~g@Wt,U\ ( WmnL?:4k׿f guJ8;zzٟIs~? XOcGO_=w}'~7[^#zx T+V!jF>?cGYe lr_ؼm^{u޽ п˷n}?'x}dd`ͪ5s3-w|4&ޜptkK`sXR)\hZ4Pˑ5. FjX0YHzF``4X.Z[[=`Oڶ'y׮ݭc3MLI$2X[$h[h hɑj9In0@ YXc!m8 7БҶޑcGy;;;*zZE-dlFzCiPhk0dTEQ2=3ÅFJ.}幹2c%LʣG}݃%ڒ+'f5شseY?[,hf|LG)ecx>8~x |o0_*O,񱾾޶VD"@?S O7uHBsRK D gR^'ltyNs%#(j=9MKpK(wlq8kH/M+x!1ZJ\9I|>'4oF-VGOp]k$.HщηsFj8ぁ䜔eW(ELD csIVI"qm<73yfg*/T*c'&286|Ƀ˳9ޔͳ@x]wyq#R XگB!t};Ͽwrv&ͫ  d!WjQaj塧@}/ BʪW#caQ z߯. .;::^8v_'&F*ڈj@޲[JUtn?׿15_EX,` G?9v~so|ڗu8E)AqZwɸ"C@&@i4e3^2<{7S5CQ3ED8g&Ku-k,J64߰@ExW-ЃO?.e9s3XC`HsjZ,*a 8j"C&X3d)2$M$dEk"B` K,W(:;S.w˝|&5ZpYeA'iLH@nEdiI*,YklE0lool5 qrjvӦ2=gzmavv1ݭ-= *#'".Dj(9#v֗n,Os~cmq- FD0NTސѾb0 j 1]fgbw?;hcZlN{q?X $Q-$Iee=6R~'w=k7NPf rL4ISƅS:>4݃X6B J/Dо6:ع,G]VVgc@3  `ן?$D Dh,?cS< =|F{bFHGěDV#!Q:X~Kq= ׍a߼テՅj+p!5?yr~dx&v߷oi_=#?-Wo uj:_~OKk?>TN&h/}޾F2ȟz'~|db~؉Ṭ( [~zST2r+Egw'J޴`Hg"젆&cq -'8 X?4j$mdYH[zWR@7_e/sÅTMQ3n RdHXE@{!2ֆc.VMKn/ü+p׏,c[] 8lDI.85L(` =rNKkkPXcKǙH+ON7k{#{W+ǎW2`Va ZcdbS2ȅ$Q.֔U,Nt# $}?l.g\.Tsr^݅R*Iw3u+W+''9ٗλ%&5oTs^p~--J%-LE/,nvAå&6G$IR8]?3otZ?ȓGF|?xsy\/S곳e9W(:J+dZ ~ڽhii[*{==2 Fs0%R.3m5MMUf3t}⌂&̂WLGgs38TiWo߲|y/wȂ}$M,AL cGPLqL|s׀ss7mn4ɩ]@ 1~O-]L FXMBчz~||p ??d@h0)2 z?F. ojo3`U[׬\/~$Mw켭n+am]Wm~緺W(!JqΖg.W@""2 a`uA 01i5u9^I !/C 3h'RJ)lvwoL7w-cS~}{4&g&F9 ҒIr+"9:B(H>r\%H3S:JBкl;Ʋy+Ε˹lvx|$$+[|)hkim$ B@a)cJ!d3Ngsb8Ngf;ڻyob|z߾=]{K#j_֛?~UQ2пz+2lsqWje63uGQ]&:M BNV_:%?yza)3yԉQw;} ͷ^=P*h#)9.ʼn+ذ9uQY?{,9~}k ZΔJrMپ隿x'3AXj$PJek>S?QDC#X9=Rn-^ @ HM0$Pi4;zxfOK" ; ΘZ Z`&#o~澁*Wm[B55S~~nƘ3. "zj-XGqFs\ Jjq2/LLW^?5=|ϻرlXcHDty~0Qjlbk'Z$Q\8QZZfm @M%IHZ$Zѣ'fNx莍 _no\{NNLrMRht8+gWނ(NZMiR37YwN6oo?>rÔg' ieHqwmVt /~򓟴L&@ ZY` P)x-5 X2M*&L[?=;و_Ӗ˅OxvBqͺu##!Ѿ{>2lW4N>wyp+2|'̉ӧ9dIJcU<=&xaF򛏄)vu0_bb2*/f9G` %ڽV $mwݷC>3v+O_Zw~=8|g~c@#z{./}`18h hV .mSV ٸ`gF8dCK`bp]Է(Ekz +W3O_\800PЁ9U8 ħꁁA)DdcZ0 jP(d Hb&K}_Xflk0v|C}lB6Sرūw]}{E_@-[YuFX$AB8LyGVr>?U12Q .΃( +WJ5dQ .,gj]ZC\zzS:j(g0gPͧA3$J.R6,HhɞM><ϷҌBx 4O;e™ɴ'S9jV?w+?k7U$r~*ܺ{I Y7-ki(jj]5m7/܎#cCs jBmq ]r׬]aW>ϔWotŶ/_~ӑWu̖Xl#Wm G/ÿ=24Qk(,&b|o]wE(3E-P33Sypʣ3~__~_dan:ƥ(m%.(()ˠa|W1P 8qZl1`` I5H0iM9;C@on4)qM=k>p; mWvV.XpWm|G.g s7!N[k956핸!"gLX,aQ&Wjmmm\2Pqe+WaX"3~K60憍dt#}+:Yf%3'Nf# " _HP(E VEqkxͷ_Ђ$ug[Ss]]]C3 3M+V- y]޽ԉ\#zgTX+exkuݚ\[)獝>o?vwoދ;җR]7)тށm6`эD+iX}WQ ps΢wMG:~ !&0ݾRGoS'~ÇY0|q\昚4%R: x,6opq!pF^W!{$lb- F7=PJ u G-@@AԛUѬNdhճL8O5 %: a0Bs}e8I[@h24{ch;'/4Bz-}ub"3NJ$V6_]_d2ia`@ ǘ0F8""Z0 xt &n宇4uq0Pf"Z6峍Zcue-O$i 1Ba0ʂWJQB %svN:=kn7?W1m7b:ܞvJ BD*aY&()#.1zޕ8 Ĕ ~JBpgc)ooOp )`-UEe8}wKՊdҊ .m'o!@&X*)2m'9T﹧)ro8 lZ148l;iRz Nێ،1]sן+ }᤺=qL+KVUW3^5i[ك3zBl;qW5? 3)pq/ںBIt.|# 8%L) `5ELH8QH$蝷l4xṩ^ )5]!@3W~~U\7JF]sn KE@uL>a7'"Z{ius3K HP8$Fw<BHJӀѓ=_.Nz~ cԠ؋{LBjyyo+g/,gwYvri:Yַ7?wMWe7qx^b<7Oրn鶟{j%@+2|[SOia^cFGt]^z٧7J*!Ag>ƶ^kd&wjx͠O*[|}G7D?E_ nyqA!$3ot;dݗ1/R"-{b䜁i Zla.  NY04cHh%Rkw[v61-+cBPVmu{r5UrɤvRnL]_0(1 S+PIe1 lvN2E ;0մƝTa4ݵtrȍOLdCHRִͮ B_Fkj?p EĹR[ 4n'\{81EJt21TJmjx0MDy q<=5W~WzA@BG ƠKf2J-wT7zo{׾͆[ `}B^zFmoH@qDJbGΜ`94mwcnG;Ա#رL v V:XՋ~KK+_~~n?>(O?qҥ7F@(0 Zޙ{^MD&5뻋Zi#_}Vߴ TQ,,gфAq6ko'DQ {pb`~e?x\>748R">7uKfK#YpǍ;*Z =ǟn4 aPsVV⋋Kt"L'I˲n2M1"m% f!DTec6\MAn77w@pᆡa96+LkRRm|E +3(hlaDU:#qIɌ ݞKH9Zah7tοk5b1FR)0 ;i4,/-+_߻{-ޑH%DJqP*Z 3!8 b4ڝ f [[fJ@ILdͮ,}ST2SvoYiW<ȴ5~X[YS:3q;Ǯ֗~_ƹT?,T˹a``1ģvo7h K)mwɶ k}![`]_{5 X3&3 e:ub5G}6Wfm% ^̈8GwRez:i7%< ~:Sr(/֚?~{CʦM58=mB ɢߎAEs)cCq$8N008XDB0씝ΜsKP1Uo`jB\6EVחl4'!Dz88 sjSƌd(% eiq=xIɭ:bX*c_]87m$Uw"0<7Z2UL+VkLЦ . rMZZ LM|m",CC^g'l6w*۩u2aBC !%(@AT47b!^u xA@QJE@0hGFI€qD!v8?!rrrR^7v]Nm9v'u4) }QύBJ)BmBn/˜HMGw^] 4V ^* 'Pz~M%d6 I{hf6W^[oNxlth\6D!Y8/x3k 0QbԱ-c=7˿/3CCŠc"daԏ00Qkk_iݓn/lns-o0٬?xiaF3VlRrxjrq™Jvݷ;c#^(K~C]\\]pJ:rt.LPL8 : *ՓN+ "rsÍp Ѫ r5N>]fB Q5 1+$Ӟϵ67?W|Oq=<<v+Ro0kvyb~x|=|qhlua_<3+It&IMV*an&ȑ1"}}xf00%Zk6 ?uGU7I:ԫc ZRK/^4 Noynnownh"JXc;T511l4)ak1@l'I.͞;~13:nVi@vmb )bA5mVZ+--p_@ݮk NPF=0 4[Nݵ+$25!`v\ʉ^mmQ܋.ܑIpbA$P@9T*RR) v@ .@CI!|?{Q!9`QM_ZZ zǁJ*H%CqTbq!DSɌTs#fSBta%p!0ƔLB\noRZ(SJ~irrMݎo"\B\*E0Rb0J&A7zV6rt'B*z*bmDҀb~6] 2#Hn\)7;{cûNvW(qLӃCzVu~aqR^3`gNlTvM%.,/s߻vÕn-HIAIYIq|_1Ju*/[ zmC)Gda'+K5B*ҩxҤr[@z, d{Mѯ|'Ukȕ vq}p|n 0!#[ҹI)h0$l;a5&VF}{FW(^^޽PvV]'%QGcxp!Vڴ%/7kCsS3*ˍGq؏~4X5J91={Y5 ` sˍ7}_Zà">TqqVFQ+Fe< b4 {srgkT6vgxѾ0=s~jvڈig>_{[r7*eEJ%7v(^ϏBb?qJE0’973,^}Ambڮ  έV ryv"e˳k+/sχ(>}%DBwl|~kI 4c_-/,fK}Cャz!.(ZH&jRs=v00&Bj[| EJ&0f…K7䮭/w1t 6L<4V˛kqq^?s܋OIfGvVUlZ* J\}Mv32d*ˇ_Ɔw#DNp䅙sfuky*"1VX*$J1Z*} \MDo \ޙt\Q22>ŕ}kU5cHe!Z=:spӡQbh._.@b⠷RÙWNO_~݇j?Ю]% %HYÓ]-/gr~gB6/~/R] +[0k5ۍNıWn<cjcHvj>OBe`aijHk4A+Lڿaׯɇ^)i$Na_Q)KS `W$a3 b$J\@;ulŹ;JqM]{یחj9>!3jjmeSg-LODzԣ fX\1@r[[u,Vmz/v2mt]n[/ۿ/Fgyugg s3ccҶ0U/m9a wQ\?n7l/fH9V" Օurp0aL0LR8!fǂs0cZjYVX,/.G*0@3V痏>rephg?&@.em;q2Vk4&3 h<>>"`}?da2}ݵAfj녑Zkr±rɄ`esc \6Y,mlV}#T?za| `Zb"/Ohya>gѝ^=-;ao0&#0Ưob+Ak4 mpM$->ķo½Z]!?عOC8%l.M?H%yZ!;xt*?l.+8yo2}^]Y8׬,a)XCWT j^WCSE׍(BQƁa.n) Vz^ޒ143sã\ndhQI˛^098vKN#N%G7GvL`TzL.G(B-/d"\MvQ .rbPVk0F@V  0hq; ! Εefۭ^ݘ,&|t}AX"RJ{TV d4%J s f1AvTfίuݞHdVև&Jl&- 584" PccҾ1"R8b(f0!n7{l&•闧Dfbx.D|!kZ,i%FiG\ ɅAqd; Ei9lV\ϋyHkYU>[q`h(I4zYo9 #D.UoTc/0cbPԲ4*hjvJtٙxzuyKct),6[Hdb(Asb_>wͮ,/cb\ }`8`hZZF⤒"b^ b۬f& `_&,PJݵC/$0@ X=vo>[' P tb?ѱW>¿_\H8stae  (D :D[sskr׺-.=szz5:23?o:8_V|[ߖ?ſx.,l#qmGmw\" c]>q6)eO's:Rk/R@CzA}CB׿q `"V)0>A.~@x^9T̥ ɂj$)֚ƼFu] ok@`|B`ȞN҃lYvx f`ìJ: λ/^篻 g/ݹ/['G.AX6BknR@((#8FHiZV[֛Tyumvt L^TsZeɐӧN"M@k)}uy4ku1jID:N2%T nuRDA-dh3Vlrf ǂK2}O00,;v c(fex8NťbA-ZH؄XB0JJypi6[B.(WVV0%tvtdU]\Z_j^0!ôuc~-/dR\> uU 24DH-B;҄QӴ8瀐R`XR"Bh0'!<4^hoT=7&f3>SkX1Diשn :sNڤX!Aik5(-d$ d6ӘaBHPJmc s߳k{'@Wk{R4RivMD:akO?̜tY7s'5G>}G(%Kx7 a!j\@^C7 X}_pkWΞ:}i˴v-fq@ Kgg_9z j\珽,IFL&MM:0<80$HAERBf:LAG5WMݜ*fw^s pG/T.#y_|%mXqϚ-G`{mnw3ALx%_1){s׶w]wH1S+!RYN5P q̵,Lb֨ &:qqjffvAB|X=9{__wr}/pO;,4,rBv w܊{2NONAx |?J $` awׁ??1R٘\4V>D@JTl!_8y`c{;s1DB"qF c X#ZbP?y4zA+G2- ęS/.uZ%8eXZXp +?IkGk4h 'M8R 8] 1 ̓@o 4%@1&W-arsJ2O&b r۽^g1'̭o ƭ8+_|їH0EB5hi3ᅭhuznSv,JfS/z]٪^899(mjL0PlT6V͚Y^U[秤 -@w%(؍:T/X9w턝qp30rUD9R!PX*5f& ],,/.ٱ_xQQ (@b Pě*K NgnmWN-/)JICv)m#:)DH:Nt+ c^}?DY87x+şYJ)ha~kN1EDz?s?cm"+"Rb hqc+P?yg_h+NB1B}J&J㓠bhvk[4igF)6.:\0#ڽ=00PɁ"}M7ؽkvq9I1B"Cv{=ss>xԍl'qJ v6i(ߘϐTRJc5p}Մi __$]Z(]}7UoӃx.< "`SRF lfׄsgsLG]tT&4MG?;knyǦ"J^ֹ0qص7Ssп󶻴T ss7xߍyD2vt_kNޘQA`2:i@Q, &ya\)`ߗ}l3(ou{ sc5yqޮU7L"X6dw驥ؗ{'aJ}4u(T5O(7ZLZb;f߇0V7З-d=D=_*RhsCBsjwt&peys>7s3's79@;F0 IRJH)4an bD-j$ Kqt1.\Q z$F˰i Äv"ƢA088E,P l6H`)@Vza@csa/V* md/8W߬ǡH%{Z PRt*mUڭt:/ =%8U2Ǧm_R"CIVZ,kWRqE~I1h&Z:`'L'fs{cHǯ/zsf2w 'ܫ]2$6WDr~`hc_ֲU.@%|~euiQ ~-Rs|/bs52"R**1dܲƔDFS}8P/wi]/]lJ$ljAg [^#ec!  K ; -,pDJ ;w;60FARSg#x`l`±2c+֭J%ȍ7rcԩS&A WK+YoZ Ņpr^7펃F.澽ƾDZTnl ߏ=/ |!gx^hI%5"E0 FzűAkv 28)zQl}qTBc4Jc0(WjlĖbBBrvB)R~𲷶r$I+J_ب՚A6SL+kJ;ڝ^Z5111Eѥ˳Z;v WVʀ( u!+! 掱]y_Y]^}hk d%*\ll.e'Biu$iP ?0j4r9MbN_Ҋ|A{""LaunZ wUo痖KӔJܮ.4JREZCi ТS?cʏkk)04I 5Jۙ EQsA$fQBt:JŌ2!fa-pn+e ✝M &FL\G d#&fBB()A I'!Q 7^8}j7Or^BWE!J=o6+AV+FyŇnNjF a~x߁w[փGmeѠopxp^^]Y" K@ Pb^},-|_O=¹D9X I X~tyVR'zԶN]4zq!47_B@Q ܮto||&.^j'vLz{唓1<{ia_j7PS3fJn+4 JBH!-hU(o_Ĩ-ZkFss1<:Φa( j' l>ɤ'zݮeRb@B ! 4&ڶ۩43#ze2Z,SSv/0űhRhZu@iٱ[[(L.֪յt:Wpyu5̍ C{GyY#ãB1& ߀ z\:dn[Tq$~0@AI"Vq(LY..nҹbi!Z)FvhOrnn,̂WoڔىDvN*ꆽnJ&^SJMaBkͦyf|_a0 a@DY]2m2kVZ2"VHr ^3C<I[ZccCؓCh @5H lnc-d dJH T$GF&w]}Uo-lޛ|Ξ< c־z}P :T=bЋcbDJ`; JAIpLp((,.MM=ob/ B>o^}H@,xhP)#P276SsXT0?77n'|򥗏7L^ %#SgZ%΃V#׸՘ Еkf&-7|w;z;nu Tb}')P0PGШVFڢ6w }7]Б0䶺'{~ka)"J) m4Zע(Xg/,xs>v!''3uW#G̡A2.Z buyRm cDJCR{=7Ѷaϻ}cB~=1=sCN&b7)B4-7AY @A(-WM0Mى4!tѱ-+TFƌbPB!go4^ϳ,(ܲm' R¸,ߗ5B9&<U'|k\6{ͷL$f=͵pW nJdtt]#/R6 ϽZ/OM\&wͷ\s-kξK=ߟph,#PʯGV#+S L-:Ha)1 ?lsaP "ioR$Ā`TAblSYߵk7_뚃6jRXkB$o>{V PT&߷wc`iutrK/owۭ2,ܪa 0 1&+ >~NfapC)"?gFGڣ^~oڿ0Z@ Bakb_ \-FF[I]oU>v`_}؇d:AB>Zy&$*ZXZ*bGc@zUk5XndV2|=Xy%9H>9x `T̔06Rܪ3>ڿ_RLMzG |z66K:yjT*_/o@6(&w;]6I1o/;\~C HmǴ\1 +y@w嫈@iq#hI b_$3O?[GGǟz LVR_M ޻etzf4l T$|,/r*CkDTq7!ljLcyfUN_<00wۚޱV bZ͋/\H(^&@-)V@ Q텽F/esq/_X=oxdpN' 6Ls\*B+5à Xؒ;/.@ KG/]~GD੅XqE)b,p7>%Q"tL3R48ПL0t:dv R)4tnٶL -ӎ+kdd"5П*o6dtzkkqGFzQ$z&@k+uT6O&^6mfLzLw X8=;{fZP`)sbZ-ɧKC} zS|ω^ύyhj|l ## <ąL\__ce2+t<^YMزR ;WJM@X67!$!cG_xqݮzHI۶=Ä|4! ĒJFZp42@60 mw_w*"ۗ{ (0 cPDs/?gV#PQSk '~Bb?o5,-=7FGFWfg iRPכlư)A(Rѹf}zy\+ظ҃J( g Z{yfyO;}ft`?8w"omPk oo[ T@$f1w0¡  sNAO0;y{&W"A^/t~XlA =P]ud4 RUWW/\uh>;6*oys?{kAkow`v5&PZvIIHJ7.:xhqaVkf,(A7j∾]w?:]`! /x+ǎs! `K @1[nootbFS>G_z;¯8щ<9Tq'gvW&[vV+\s[SKĈ`P-HW{;zݷiK+3G(F@, `ْPo~s_h y`NX{(k]P_  )I01' E(V蠄r+ b.̯l*] aW*R%\6KYkƝnZCv۵Z FWH`P ͭ?ꆛY6;o'5RBM(@.NR*96 +LE(H錓/nۖczxij#Z5:T:[{~!p)*JRʍ^1BC#Bf{SkH&, X ЌP]/dL9?{NVmsUX|ߵݜ9dgRn7_pR )0lFQ)aUPߋjX,9PHg_x꩕ٹt(BJͨݽod\4TpCj% Q--noTk81\E`UBX'$D }#cǘ6v 44x~nⳁ 6s3sSt箝bzORJ/_\\؈4ɉ]wx@#BkJJ(44>}{A[7*c@C,NOjyPhzv. 4bTZ!6)SRhd  ߠXkkE-l2kzy3Gᄋ=wʏ|o$rFz=T=VԱ1aGqxhǑ_vZE$y_z鄱֊" 6JJsΕXN?rwskvkbf KqeXWW4(si'm畧}G۝]g_^ f.w}4d֚sLTz]!Bra0@2+P TQ63iHBVW|7z #@`H:c۷#5WFyG[m@@Ş.^*JC#;Ŷ9c~^sIר?ē‹&v5Sڎ'GW܆a(q6-vo78F$U#o:#(F f RbRiX*(611ξ|akr덷N]Z?ݧONZ.0Ɔ&K_`"V^3/[#t®uZ/=x"YNZqOWdGcjoHkk36*#_yneq#?ןcpd 07c;JT2!R %IOL0 @+)c aΥ3G(0"2v[ "%kSxOEk0)%A 31uFfTm\xJ>c]sLF.We;ZqJ&҉DnűCR"jѱ7S}1h&P LB ީܻop444<6*^\\I眱RL'E,*N~$`\AG!FB‰0*W+ FRÒ#H$^qҔR* F=48|?[V2#QJ(R#),>yvmfѩהc4‘\{P+psY S %WZPvn'0EM/!:=}z7mFlD(Z A1 ӵzjy2V$)A 05L1"# @M+kkcL e(P|qGRr1hI FkE P_c:W80M0NXKve4 U"&A8(xk M\cTz>5MJG]p &ۣI}C$|ϛxX@XGP!]L%Sz#z 'Usmw>v7K0?XĻ<}!T+B &*A(1" <+ozӑZU^NLL(x9B$<LBgbrb.%U$miٗ孆i&Rfdpݵ[x X޹|yRRLi|BG> NGp?xi+|kfiq09Y BL Nrͷ6תO>r$ \̔ߑP0"]ﶄq4WmW UB@c4H7FWr'*La /T,R0%Kd&x?V@ Zsll:50L:tksc-bic(AR c.-"Hh,`a`bWw?ks0/T 0`x0JiB`vǞy!Ҙ[I++u0 3ƱQ ,BK a[.3x,H~&2b 4W`DRj{}lq\?"TRbBRR#RaVRcB80L(JD<޽wog봌Qtaf)7^(e8;58x8BD2jlƜ8sZ[}my{nE%,B<{vӜ v}oX&25 D+i:Z25-U7ڮkԱNBJϓ!AWO-5`uZ:7F">FwI0BC <sn+ '@1Z;fOcdXc,E!#cbZDpNVoGƊyx@ 9g(^\YY)oBb? #/}iU{'tϖ E"NpAȿ+sX۟\8uifZ%`fGP,ʥwٻӞ {YO<73 ܅ }Ã:Rk)cbjОxDG}f\淾 I Z_'4sA2G0%Cm>_jƆGu˛k??D C`VzlG~_?C&H %!nD~a/< Z"(k% R啵z5 2Zn| 5 ҿA]y`%PH ò-j0@! X {oFvۡj{tzu_7RҠFH@̸_{/Jml}# LBUw/x~|֠#7\$Rʆ-sGNh|RbB%̧~rs N0kvLbRFwNDTH\w7&ưAG q(SqF*Gn?\+P^}ɉ\?~ѓc?B](Ӕ=K/=*@&j31'Ne[{×Ϝ߱ki'U7 FI?K5(D$xasRDjvoÁ?GyknG8ktT&i^51K9vZ^[=y勷|ӑ&y`?‑TuZa_[^'9߮! +ɯ襤 O|wW6) hHZ^]B{,oIߕ8r* PZhґX_ZZŅRɦ ̠e`Fdbb4 "V(V.[zїj[މcuު;//!4*|s亝u6*2mcVjtbY饗=k@Go݃~m Y-cecdwءC5W_G~C~_~ `mmpc@Ftխwgxm%)+eQ \Ltın_(脑Ƅ Lƹ0q&5\ i03Q_uǞ}@ H`ۭ&D) DHK%Oa"Lnsd*_*ߍbVZp,US4̔mK@pQ˧-#mXJTW1\S0y,j[j@xe~ܩBan#"F\ p\ѤYnןyt'wIza[ݎ˵2m;gf@AOJO-&l3\ <k'aX"pK&\ mPXDȮ2M4 !06b>ϤsD: JLCc̦z] Y5"v[]rZL5gVe9q,cQ T\%0$@!C X*F@:t{UJdw8o٫KV=Ti{j5jzy瞵Mkr`[o0VⶻI&L{eyw⹧PLXOb.wMt1wq"0q]).x~"Y rbA f6 ˌqP_%#_zSXfJ5;] &\rV,RT( ay; A$/\xk2W :s PY*w׻vG) ?o=s,?-r?Q:vɱ٩՞dib/8siV EljʍfvvN$|:cZKl@@z;8qmw][F4?>uvQ(e@E\J0%P,A .n'a:±ضvM C#RQju%uIX;uۨG^wsm$xZj-+b> b#,h`4R@(qɅ e*F䲷{udg5aQKIf X^7mǟR+>1L%Ҵ(Z*`@S[l_AWsbW1p!(Ips>igno{I+[-[pCH@KBdfiݽ3Oc9׾n9}ޥh1.@ 0Qި>O49`Kݔciu4PhB܆$Z so &D@"GF/tB _2ʖrvK@ _ے{ B7]^.oٶ8Wnܺۯ;Af\l0ΗkX) Rr(Ͱ)7V_2~۬LS\/* rTܰz`b\:`-X:]zO /M.hr36uiۖPmr?ӠٳO}?^T&gf,8@7XGy޼0qGpΟ*}[kq\D'/ju>ڄ H?o7 {zZ7_Ŋx =qy<\zn򲝘r u?731zw|m(/kc\nFqb<)ׯ ?_ʸ% l=ʕ6ڼiv΀^jhcίYO>ܒMzX Ooj֕ }O ln-/! BcT_q}ww&rf@¼"QL RJA@y\23Wzb (kZ%C< Jwu@)t u[/"AZ[0#Db观zeyQk'>3<>XÙu 㳵Fc`hhPWt+I r&ڪeɽ7=SpEvF:Hz)f3)bˁҾѱǾըFdl$]䮄"Em(6HH1Hkc[j ٢%`vv~ff>՚kŋ碨ATHWH@*%&|.`b.]誗Y&_X kӕ9Gqv\H2bf-eᑊQq$VIӁ'7t9xEU2/ ܿ"uv͂M  p,G7ƀtM;BްYJ$`XRF Qpz9 \)o-,yLxL&Zd=vMWdlG0?7ߨ7(V/ yxV[\jVYOir=ݥo;5jp {(EQjhFU"-d{ *R;W¯v5TqMW9DLUF\W # V3Js(2 Г򎽷[=PZ@xiҞ(N? zE?X:lC܂BYa.H˦vO~k 'ӬWtiOXۅ驙ГzǝX,,߸q 6ZˈV+0P(ܱS}ӭ(ٰ+Vi./#X4(d)i5q!DX.|@L:ssRwߦ-[xR .?f f! bw)UY\ЪL<70W2Ɗ]Zhggτ\>{\Yrݬ d8"XFIJ3IւK@H6roTDsČm%Z'F"`9!Z6K,T[;z^(7jnK[uVl^s e@TGu]#mQ6ݑwM?Xkr˭W*3g''8&`$sD6p:X˗F/m]nZ\&!Rjl4[mW-JTb'ȨO^ &4Y.E>nzYm4lscN*(,=m45I|o u~^dVq߰zK+څەeĵ @x% }{W>Z^}.oEt&y fZZch* wu[0DR_:/~񧞻8=@ 9<8˖Ůo|矚]r'&4zfYچk- DDJ[FqKj'yXcRb A@l3OZ˕B9g%k06`@PE k5g\ȑd\hE`>~aMYGjۮ5CkJk;)"[]b2s]&J:(π2]:vđ3l1*.o6kf~ Mzbݰ*K]q^!:"4(`H& #곍P\7cs=wgzT H,3Ǐ^x^+-8CB"Z#:i.>y虧r^z^Z\eShf@)tޝܼ)7RFRMx_G]KI/@dhMF2w$V%0)@Oδwp}ott4H\mGL*@U;P+W';\5s]:ġUturh ,1KdI, W)7Nj`  t`FIq$(N(NtlH3XRhۗRTngV.kKͱ˓N4Xf,q.1Wy{J ,Nhk 8B_ ]!6u7Y52~;x~h5j'O|Qku RӧO_t9.0&!ifj#Xf@GJHdzUZ,X匵Y(1B\)JJ] @1~vb"(i1aM{AC~ڛ |F&\2!2vU"NUKM``ޛ@oPj* cQCdT.A)h7=ױ{~j {6\z͚UA&86UE)@7e|=}kFPs_9ݺo_w裏&qȥ344OH1htDͮ3nE$$7:f({qd@xBZ Q&jH$ θ%ɢڭ6e%Jkт 6Y/*f %cDkH $H,b(nٱv][{:Xi N@vl[zcf6 I$d0jQM2 t!{~O'q`{{oj{V#/_8}3Z fI5HR:8x&A GzY78d۴Psˋff.#@ `hZ6ڐ2u\05cf(TgwsuN՗7n$W>pr)厃wܳ;kq-΅a`5*(aqYpnD_x==zn/8{ڪ6ڑi_:짿ǟ}f|jBr_*fz.^83nah1d\Rp F)TBőqrJ7?PXw#c|/].u[Q+$tl*mDbZ{7om.73zެ+1@5'/7sb:FQ8Rވcq֪#j3."Q_՛ʑ5۶Z~Ӗ'汣gZ1ւ@ i !-ZWr[dDh;Vd>0vsED 0:zDkH,dy˾^ }bh780 m6ZR]֑4E`H$b`!A0`xFV.)>juq.\x R `wyC"cB 4$xR!Hg ?Oϖ~Hdli;Lkoفݻ[^1&8Rrc xƀ _vuL 400h97a:\D0rv妵z9c@v+=яHɠ K,\U"=R*DW~ D2A 3?s='N|Yydqr|Cz[woOZ?rQX}˦,􂪖O4T'ٹD'=nŠtP В/VZܚ[nЙb\EoPF`Iba1 ☆W/\34[ns> = 57o Ehc6WL yme}-z, V)@"Ϳy2C`j԰` cdm!֬<}}￵֨w|]a䂣hju+SSsKLU*2k-hB=tH]U|{Ę3|!.c Ka+*{14#Ĩ h8$F̐@=sG&Bnp m:iyn(t"c%/ ®J.\[BM!CHl蠫M`mj(\ KV $045F[`fFFuY]eo^{_2F%2J)I$JD1BDIS%qΐ,YܗVʂf`r9c,V"À $&XIc~:XaÚ[nڔJs4* OŪUm\땉3O,M.0B"aX7`4 s0'Wg=oNmD|h OL-֮YcͅO3̵Tk׽w3ErtL"!' ˮ  Q.zL MKJ˜O<^x1!YFD~g/\\ Zy' rRiԛ*4Z@=`s3kݙ,jD+qXV(I@pXKH ׿C;׀T pUrau }𻴿D|BTz]P50d iַO$K p9cWƮtwu.>s3 4GGQP$,-ΖIHqzGz'N]XZϦr׬ݼa{c3/\x>Ki /|rRFƕMҎߝ+޳;Mss7:} Gfgj<p|b!c4}n o|jtU su2q i9t;N'Y"eO|c_ ^ Jφ_~?g'g^X!ԛUm@CܱcȎ[bwRyyŮ۽I%~'|G~4 Clg^8 /H` qq^zŽvKr#ZnЎ];oh`a<4P)`6 9he=j Le8gD~$㵯~9 f1 XוHF#f]3E ȏV_&fgi2 5.rj'aO}ݵ$ըk4mvmMfrezb3̧3tq"2Z2Od,Ed ,X`AcX #Ւ YEhb[ h. 1"o57o?-!'2@bn^骬zֱ#^>5yR:Gϡ -xၷ2+QcRW=qX*MNN= |=rmzf@6UbL(0";߱-5YlW9 Xa c#Z\>0HP[lSA. Ƹ2V(!]XUD@`-. Yc4B`HOtN|5a[g~*s⑋@q!ã tZ50[>{ lݚ5bu l50&2ƈqov2sL ٵs۟_]b 8)Iy!οM/yzT㤍9#f3 U_'?جկ\3?S2$Y SkFVؼ &㱶_XD,2CHKfjE EH(YVϽ|rd9W幹o|KW|}=q|Dq_oW](""gL) _1͹.W?cP36ާNlboƚVXȏ[f&nڳcϾ<`b|ō_RY@< Ҁz- I8pࡗGd ü| Ç^:p=]ݺnz\5{ [ 4FjF8a#"0fUXD Bk庛 '13]"G %$L aEu-l6${73vKR.-Eq(,!1ǔH`}#C61\$|񒝙o?FHjN_Ih}N;g&&֣FP(Eeܢk6 G!NOX0 NHqX,04p!X)MWۺiӋO?(A>j̪^w#G'U@lEf$uj(WY=i886&dJnY'߻oӖҶ9z<~\ScRx*6c3#D w{)\Oc:;f,GH3 $Ne X e:H4;5Zjd9p%pCN W4;fr\VY/`ܝӳ/^8:=>ebR.WM]9GFy ''ں;<0Dk-^lCxءo}S3wu2_/gÈ!pY3֎'h#[O{Mvir_toXڸi۞܅ @C^|qb|+Uwazfq3հ"bMK8$Oh6"hj'ؓOVV7q+Գ3Ņ 8|ۻ{g7}w@_IW/ؓ5!uLV}KDF׆nJ\S2P/N>>x9WQ^n:~??/V Ā8l5Jb+#dxٴ`p7TGJ_%nEsg(-bXSkL(/tvo}UYg S qk@[k4YH"FHpX- e")]@3"1XҍHf2ëD]3Ǒ,-B$D[}ttzqvзGmf''8 cC4;&" (6h,%ٹ'۵C"!~*[zBh^^ʾejuS#uCJ=39/37]a]!?}l\q\u;Ʊ+carSǏ|o]gϞ%t:}5k /f[7n=u&VJ%I*3л=W>~رv8%Җl}~)YtlBjk'U^>x̕4fkZK' ĥdIKc @tQE C0#[߹P삍Y&"qn<*@bh:~;e$dyk1"̓_^*uݴs;!s\iS2`*άLN!=70Ƨ_x湗}HG>\-׌f//Y  Oұ.dn)˦{JzA;6 DNJiZ_o= Ν/_ٸO[QHe;!K ({sF"dGg{\޸ycG<܈c0 XA4f:'qvTSj`{Ν֘M'ضa@3O= t*a \*@z|b+s3s#ébkR:CTZI)i-CED!IU @<ÿ_{Zul("Hzzu[b`0֔UwZګůAVx2 G]QRs_ҥ'q@1h/jD#L nj4 H@NWrmDZ bek-g ,b,CKx5$!,h@7Ƭxx𡳇/{llpx}xb\(`h$3:V幗Zw|{@Jn}GVkgNf PV5nJϤ2٦QI ޾NJ fY2nٻmopOicsDOO-] ;h%ϒyٴPv}~?c$d1b g"y6" 9 +bYj-r~l?tYd=flq6l^c.'큊̮]{##ku,tM8xB {E!b6B~ I+(b_#_ړ./Yub$ ý{o[/>/y]v͜aB0$t rd%ChҦVq82p6y׿np&2Dc{ rV5@[M8c`,1!-AKvڽfMUb|;gfb(sK H6fyb, Ta޾c56cȐ`,g %VZ DuT>^)G ԵzjXctt̅h."3TH F.d'ODB7ߓtη=$@Il=>=:MO }}-0F0`ҫ/ocFQ q>J?o( l@&J"X ԀK ܁Roo"fު7!WhRdTJ3hYfұ>5ѮLΎ5(Co޶}T -2ICsR pXR_Ej j?ԧkƦMwyRT^<{v3'73oy  0rԭHk9}FG;Ï|[b+/rc8 s!փ}x;7غy60W}~G-C7{E#37޸a-c'I(0` k7 B<uµ trmvqFBm 8Ckmo[Ξ?WF0(o,Z=0cjP+%ߣ0iU1IųZwͷټ $pܾ-Z vϾo! 0`om`KZ PLN,-,-E়fo4~}C{ocmzhܘIu?So8TLqz$aW:sف[T__}V)L !RJkqgGoA36A_Op釨I:#t\n!CnØi̓tX)GE4:pي npnp]ŘFa7X:Y Y"lZMv ږ8'S)ogOxb.ݹkWZY^^^fն*sj, S,Xnxt<W;΄f!!P#"[al#2u}K'qV2( 0$c-i,g'sFdLKnn^}4X$d8B0/](zNY85z* -2(/m%kׯJeŲ5<71s، 23ź;&õUR_Z?kZ,.M_2l@iחZi452Zi )@28"XОȇ?|wMLNM:(O|63W`fύ>'øU&~g󾾻{ӽP%r} &Tؔ`Iv-`,zÛOk3sKeN !{kWAok@it(ā\jgO6ꍮ|Rn)*}_g7=yϽo|#hsEG??42<}Y2p&-&Xx@u+M%9R^3 6Dl?zc뷯 GN?9Pgnn^(f mxⳖd#ldفt~ubG/x0Cz@vB ٥̗VI?O~۩@Xam+ ʠ%H (;>J% ?OONΡrJ\nh:Mi"AzC-~'qCx.!b ׏R XPJ scV+idKY8e WL7N8FXYӭm!ˑQsG,v;{&`c kYG9HN )3g/_`i.P#H2~"h6|89>GD2sлu#, )ڞnؾs" ̢1Ɓvm?D~HH5' 8~露1DNAFlںUp p3_,Ri\ D f4(2^u^e.(5%J*j ͱ٧}7G''V={@@Tqֽ̀ortЁmS>8Q!gYBd@D x1Wȹ әl<|k[381dAL`ٽH^!E.;wGYzo pt9/Yck~ox`t;llśn~.>_/n߽=2w&} hV;{2&¶|G0Fk.1ƑՃrCzcLS_ڵfZ5Pl)Hxٰ`jPёÇ'7m۴q@H $c58sࡓ|+,W RiW zmn1Xc+)GY,k !6[\@?̳OsH }gOrx}\,\'` @$ͣxxGgqkIh2ozw . `PH m@_wM{vT*\*oݱ}fr&\cj5ajd\m|nljhMl75vN$,# ZRH!pfkX=?W:!pLeX^&l]GkEЊ!J=`Hsnݰqg?c*Z(z6|7HTJ•Z'@ 90V2A)& gZƑl.tMnۅo߿nc=uԧ>׵Zcl|2ѱDZ>}$㋵';GW yǭ7\m۶ԓ2ơ Ts|X3˦JA-ԭޓ,Ak]v5W7kGCaHDA.Z"cp83F#5jص^-?7PGxP?@ΌDQ:d @"cvS #ӨV*+9d^$nhn7֯pȴ1hqa!KKoCyO6~_$!)k j)?Z=_ Ɉ(j%@UHm nqír/;r$C4p`o-R='Nj8ͤ)IFR5 8Th0JC0.O>أ#'>}W-XŞmCѿ]oYK[ :$23 \]_F pV3mw n!*+_뚄QbHTZzm/3 "f }_7t1snظ!ffg֬@?g~g{ŊiǍ ַ2 1\H~EsdL@/z~jhiۗM9_g9zYMD6fN_I0΄uWGuX^| ʕ ]Wg7$a/8q8[KF|&%ʱI8#6t}šUCczCk6l.O|iK??ݑo~MSg/}!C.^zNuMĕ# R^Huņzů_sy'bbkjb< Gt~- CQ}T;;rA3i[W]2d|A2uȘȈqcVr3 NXXw|g[Y wȐ}'w~No]tw.H;yG$cL-n}.]Zeuk(7mCǤAl;8QV*zОM5: ۾zkuyٮ4|qn{8p/E.hqns|.JjBa~*ϝ=(eq~߿{<Z?ܑNQRpɿ|{5qϵl6^n؉+W R&:f#h 9wk[@h9q cߋ4B^g|aY@hhexݎ$ Ϝ:mN@#eJ@+um__ѡ{cO0~?<95۷~?vjIlؙ4@HD]vu %"Xcl~n0On;pʥť&Pq؂@/Mw}oF/kNL#ֲ82ư ~=wa$ _q[=Ut_a ;b8mQ;7@ƈ1N0sr:"EstK${d=Ӝ2^%hab]524P*nTo, ɵc 끐I3IֆG/.tYӈ5CVgzK$14Iaՠ_l, AYI3EY{2նq5M"eZq:G" 0/g?JN?s?gw!_2ׅF6)o~å=}⥱񅉥heK=PwrM]|@ 'իlx]u@x= g3c-!CZš,sg$y޳O4\TM%:F0=Ax؏@ @p\ϧ;vDkKH f4&*-tZ |`Gk sN׫hA%Mk~I596eIIZr~&ۨTuJeK iݷ"p7G#k~'ٙ]ˍewW #ZĎ6;!-=$+wXͻ7#?w~际EUtCg8jsw\837y_fIbC} !ǖ.Qw):.dSh0S8][-jedYq'Ք}vSbu#nFѕO:V]tHX3 |SdF{z5'-X`B5ViUDJ7XGa]mY(%zrvKj60sԪ2ω-i-&H$iX Ʉ4IId-!Jn8Ɛa"Lx g+ˇRHDd&xݭFEcny}ݓ {{zVlݹD#ࡧ{vvi.]c\xtw=?18oAH#ƣ?񱭷Vu9=X8nwa":x+q+Ёv\M,άQ;\H&X+e |Сj3&Wxc۶o软5s&Iv<ЬpjU7`9TmZ^h9H(rɁ<$V+,hՕ E4\[Q, CzpuG>_sRGfZfUpCd #JL Վ*y t>dÛ@{z2#K˵JT33@\+ɔV*J9_|087*!At*UZt>)c`$r^UvvXU,U-mRF@r~gH7;Z[ jn;2Ȭ8ʄ"Ǹ* al4pՌ]8k5č+WK+!h:Ԙ^>=zЉKS"!#ۉntcm{6q\w0&eŐNI*vKBJ ljU3R5׮馎6[( B @"HCb)T 66JMH99\\^05&5|@kۙQ;p<,x`Ro޷# #g!D f"Z$BnXh7y?YN@xv7ޑ [vm/7>ۿρ]~KF $ MQ!"pj!]'5^Dtk|_wk31>EPȵZA@8'$...O^^,p/^%Bf+sαl\׵XKdm7Yyy֮p5q7kywp@. TE"[IxQ]^N8–,!*U7+0 47>q H(!|Դbo_ha$!ґK3n![m֋sڕX%HP#c8ZddIA0Hi5zzP3nD70q'`($"XMVXҭ'mC`/^\sfM&[.@d) 4Q.I c>S$ruk;f㚉+qԺ۳ݹt|-n/_Yx8? D3Dq6_Uر^l|RRhff}oʽӗ -r jʗ>6ݶgo (7-Uk5Ɛ;iFjc,:vc'z|k4Z37߶öϟj7=桑Je2"p!wG!R74Iga\o/}3`(Wv-[Ȇ[#fP׸ !NB\p`& Z*FBo2?8LX* +A4hT6nO[̈́t=IBS@K?3?2~ib͍rszrHy_^>xoȺs LLhW̍+aztݎczԓd"0Oms7?߷ޙ'vl~oˉ",jCDOMA˭۴}kEr=}B#ÍīLERʫN+M0"2'`gv5/CXM %|LNws/_<DYk17ӵd{{IgS=RgO޴v}>[^6ͥمV?v+XH"ʹ`NeZ[jg]9\H`9J΅%Pה沁K_ IђsWHP$U I!*fc$ňsKHdH!3@L@ݻvnߊƚ/|e3%(4`҉Ƕ1gf,^Y4?~ĻFh*׭+l?KW* QB 𠧻 ox0i(DG"t@S2S.vzE@8 Bgfݽ||Nw! nZ\ҮW9!\Q+d*Y\ <bV6j&~/=Rٽoς ( `A*E;_ӟiڌlwl2rl mDM$^#XkN) 8$MǺW:hxԊ\k4jsQT0tF$ |R,#@ҜR !kxU |Vm_jv[ ϟ:wwSS)Wq3V }!A@&U^Gˈ.(X Ƙ rL#[ К>Ͷ~G֯3/ǎG .CD/ڪVt֛ۼ9UAǚXZ 7l.8 `92NƮ&q)NmB"On $kL"$:Itq/,@ YdҠ-h hQNWlN6]HK>KgppF;)3Pzݮ>3֢ц +-?Q8L2yDvvflY]~7hk2Hq-4qg(ɰˈ,8$D .g֬[7B6g=kwr]tڅvGjr|QSYfmHe M x9q7 ?uZYT/^(<1TĭtZ7ǷҚ):V"|O1Nn"V\%w0e.1ve\ݴn}X$q0ZQ% SVRkִQR5>ɋq~zڒmm2viqzxZ?֘mk\D'И#g`+/_påz#Vmݲvn'M1 ؕ}C䜻Z%s@d|WM:5u"J h!q39KckWa @9:%K \ `fmRW۫(Ưű?s_y|bTΛ68i1a^;ɉ >> g@KC @fǺJ~ֻSNM˕6HΜ9sl.OΚrjAeT!e"DFX8zi G=$Ze% ƴN*Vt$Qyqﴉ q]k (r=ZRjj2Ah΅qFXc#$rc$I8ch82ZRTʋ0Z^!7Cé Cjϗƈ6 CPy_-@ LTmt f !q_@68CZMFcr <(8#Lk7`v)8s'hp/3SgH Z$Ѥ$҅K%d 2^BJ) <{[zRAg]"JH3dh8l4jKRbh,uJ٥充'fLeȸf5hMn߹X\pJa5QЌD Hc8G&Οc\3_/l4jE[e=ݻ}MJ3;FH'lf3 ۉBanX9 0e 0P]B`PYZΤszlwzF3fp`W7qb ^qD+jsVI9/>OdWšj2vr:߸aúk5kf=s\L๛6~?Ϧ y(D!Q g 5nlRcL:N(c-c\ͅ}G'JOY;D \`I'ڹkז[lX 1+KګAdq؍1i|qRy.} Ȋ,~> FމCy,'ɮbK/|{>;0D $rv>Di{[ ΍Y~EN!"d,ڳo{*~ybbRk,(QI$;93[.֭ft ;R0l3Gkg\fVQʐ2dupQv+, 9Ͱ^HDH8B T#8!Y}DI3Fv=#@;l a8 Jm) 8Z%RJb_iCf:=]ݞȵ@'$ /T5Q7'GƁfenkU=+^Yc !Uvec+=nc"IܜX"!ZV؎RQ 3d@H$AՊ͎=Ź  ˋs` 0 xCwjc$=.ov9*}d{ 1Jc5e[(vwwO,N,հ]BVY+dh%-),R DKW"%!X}L}|aX}rH:mJ2sbeYuFq. 9nu~IId#Zi.DX x,>j-bҕRa^[ XtHVv4qı"p>e˕ZnaȣHMHo[z㦹śoݓI翑{lx:9?5qfpcB66’%([, ]fّ!H?Atv,bajAMoyhfn|p[$_(/VrnF"߽#o:A*N຾qEoOO\4 4UҪ$rʑk s&xTL3@7 ygJidl+B, 0Zs/`,pQn߲m-J#σ#} 0a'hک#焠N$̀`>0ƀ!vq'I p`bzjjWJ9!Pc=Or"Kd].L,N滺Qzߴ867^݃oٻЁ{Z.3o~KS\(2Y.w@b7חhDZo"ZRC7e;Ɵ?{>J /Pז=@\+2Vv߶LGLE;@݀{3ƦT:Njj6]gfz)z{ZKkma R,6F+c1ͤtW Thvn %?|.4ZMšuzZ/wu]]Nwph5[##EǑ兮\FFv9D)1DΥQI ܍Q}wlG0k'H-8'psԅ3ϟԵ%P$Z1qw,ryyijm-tٱ-F2.NK)<[ZZכJv &F ;?`%Z9!\!6[-v9|nqquv;HJGqh4jJN׎xGvnZwl6.LOONNOO/-jXk,#۽g[Va|W).Z |?IwzfnVmp;ʨ4n _27?ύWJn )N1ZV%\2Kf Y+kjkfמ|hayDbjaE{i5s+$[~`Mv޶Krb;u~ƒps;Z TfmH 6 *1;H ]UP}vVF #D#QNSi#lZdR)`Yy07Ɔ{R///884<_]o7glY L!jc5DuOpܑd |@.o7]Xr 3H Fdi0i_~@; PufFmM;LTuIr<ܨwsqE\T\۰Z(`5MǁblI288X.WftzB>nFwwsG ]Z&CCIkA_HoכyXcs"n҈ Qf)e@1FV0&!`uX//-jU_ )A) ls= Ylؐ_GC4 Ոg.9ۚ,_u0:(6 YkI[U踮:Ŋ{Isn]_\R.O6UdL6ϤZAWߪ\OE+7/]n8mb90@tZd,CmӖ#gƑ3cա=}/XP܅Zi@Q.))a3twgWO=/V"`z.XY;o^= ?ЂQU((s=9)Q,61pC"PN3tjSR1҆33sh%I+N+aؔtq,%gc*j`޶j=XH*ZgZч3gVKgj.]8{<8O?MwYՍWO|9537pk?1cX 鸻We%~{B$w,<1X+/x3ST!\ORo})~ssVٿGȴ]Ռ?ihbӹa QTz4 -nyD?S?z?'g 7P 3cϵcޑWwtveՖ[T] O-,,MO^,L1pK5[ {.R+7eDܳgc?%gϞMV3}[,gmkLJyhѹLF["B7>~c7 !UjPJ.IhLRlF RLKqb " wdH6AZ fOp3KsЊ1LL -X"j6DXӎՅ*dV._9_k g܀;2xP $VqFL0$2QgbDfh"}Y2ZB֖j+XWCpXkkq蹳K}=alhZdc癶qPDa,E$!Z#\B(#Ȉ T-_|'W SnE'ֳk׿^vJ)JK .!I%yGRs1/j ΩWъ1#$nmv8R(0Y$߽~*>0ݹo>ŝ7Os/{)#<@";uW;^0D$K+e #Xu{׃pbVk4|k? H6E&j{Ο9׿<8-r_MTz0h@|cȐ:cՕ&P)JˋDmA,EƠ\I*B! r=8vs'R@NK ƹ*“BJ!JD\ i7^SWҖ-׭[wK80'Z s\O~62X',$Dt]tir'EW~ŸDqRVjT\x|lwT[hZvuSSWga)v\'#bR: 2$ pta-1I,X9(vب:잎5p}MVҊ:IOS4f k;,"Ymts΀0fEॱ/kMV؈큓5Z;{*t %:5>x]eufL̖V;) YE3nn*$SSS*Zu( !"&#WGKS2ݨ$Iv3*QA 8֞hJŌ3"KCC0 ØL0ᖝ}~hafшѶCr' m6u&m&MMplǶ$ɶF430m^{H#[5z9s^p˪ܕI-'ϝ>76tzt?x^>!Ѳ}cey.XBuXZz ?s{s Rr Hչ ($@HKAH0k}W׶UnmfxZ)ԷsϪkR~WN<%'i-i78JZZ&.#,E9Ltg%(<޶'V@j#y2CzgB] t<239pLZBWȩ??sǶRyer5!hBK&*ܰq "6|3ouĸɸB8 "ETZZ1$Ƹ*JqΔR!Z )(A@"ji|zqьe A!WT( 8gD^>aAK(ItEhEX\@L*fꎶB~əIҙP ߒu:+CcC$jszz Кr]eE@4W,MDM&!!1d(Na&*$G$0 cqDM' dpX0n؃?:16U,WKRi!DvA*qŮn@ʽ z"-mYm#?Vc hJp?W|Ǜ<_;TR0Nt 1CNRHd` E@eN?Edlg[Gv[ -nJ ZYE|ZqqS/ja-Hؙii::$ETjDgOkffg*QbufW(NkP) c6IЈK{Z`H\ .6_-ӑFd a8!>'#\bxeDΠe34&)Y" =w~ w< |{ܱ*H/ m\ {_] |KqfKEyw'oGbE`Qpff-Rz :&X@BX@9Lwq;_kԇ"S }s>0}cpfz B[:O=ʵ=fHLK8qRN FRbc?M8hHrbAdؚ8K}K gK7ϚqRC!lKBQ4G8HlM=NL2:+']]&1&P%jjs33g22@i߾Js带XHZT||ڕ&;[ *\FGIN9Rz̀1pCǎttON:t%0 (1)`rM{?^slw;I/EH(Z'Ǧ60O֪;JRrnvf#(dQQ(@I'u~!dv`lMffkne5մa-ÌI<~!=RF2'I-Ϝ9S*qʦƁzޮK}aKanPeY&   J@.~JreFo/eEՖI\ðW˵jI!),sTL;wm{ߺIoog[?W7 <~w'虳SW^ DSa1ڸnV/5M$5!cRR SY+_1RsuKJzH/~_Ro|kr^;X+ZˤAXe ֚f<%>h?2‹Vz)E'P@V&L6Eh (mX@b֒%`$&ũb Qq<5StنQevjܹ,Q ǖt&&eT82:213>WtJԓzJD@ 11&3UqjFسۘr& X) D`@Cb@ S0`:*'ha2'ΞNevo~Ýk7|q!j8t 704COZ&'K遡9"2g \v5[_w]ۉGtkaD4?W亹t{gh"䨴ä+C-mmW]!z>9=1??/h$\.ɽߺ{h`XL֪$@bC2N9N8رu6l\W -DT*Z[Ɗ%GU+}ms+xM+W㭯yO ydY7H \b) "\@$_8fbAb@e0$`fĘEf;thm|ݛozߕ3׬;x{v}˟?tЇ?DSDͺHpP͕?kskpԶmm0Tߺox'XٿCC"Y&\RO&wgkq5L,_RhO2TIe2}MW|,es9^/X%UL\|(,X *Q !@;֚z%.NGsL-όMfg"0XVututvv~3EIV,g8+ybα!,iȒ%@DMd,f 8JGx^V}RX^aG*JH8WdH qX6*' cXȈ-cLȨ\.8pV@G=v T$`f`2֎(|o(ʅ.wc{jV9]p'7_ $ ]ֽ{N$m[W\@ V $ wծt'';~~xO 9[/zR`"1iGl(sRb3;==9)+AdB]8qMg3d oX21w& |r(.u6dM$\Jaetv1oCg:= B@bR7z4R]ݱRlq e;޴qoN59ϽGoÞn!HG޳a ;  l]|Xre]m¦9R7PIX8$?t\_yGSHc "!V0L@#Z\fh])=έX+dN#ԁ'g'%7ӝ$9 Q(@qTm4`b` T*k뮭۷Uw#ђw[/v.6g1I ZYחmDK8J2d);!;W֍$aKgΎ:~}?<==ݽ♧aQ\ڜXȹs=lx ȗw2f"Ŷ !vE^|zi12֬Y~O3cu^Tm87-XB0FhH#͉P0;!0"]ZryyX Ch!Ngul6{kd_akV^q+n*Qy&iۆL&ѡS'&T\Jtrzu]+<'EIm̂:8uwuV*3N;otF/2n%%500 @ġ& Yn Zn9ygd wWd[N]Zp->T3p$֍-ΗKBW.:" 8qGΕ?3|Wzw2eA%DH@i} `0s|qշ {0'lLQb8s^אL>Rt)sӣ3Ɂs)/@ J2 tJ¹;$dqd Y֩D8,6q8K YaЀd7  8ɸFDim-y,s=HgX7rE?>snݶ5;6m}fƫG;篏vW1Ƣr KJKI fdRn{y-LOOjZW@ߝO+ά]38gKXzȎ.\XE@G_591Y,[[Z'&Jܢ4FȈЂjjwʈA11 a6\H!]GB5D;=̤]_nI^a"jt{\8\U+:rԾ0wjߧ*8Tr~^*W CYRqCC'x&I*$"`XwUk1(!|!oMT,I v#lTgRU+vٵik>ֲ[HR`^.KH ~i&c att@_Fq5looS+'K.xWtΞ>~1Wj5ٟM~*׭{^<Z"#PZ/Zm? Lӟuobrbt|i.$EMTҫk%gW5$c'dT Z˭b` f1 ttE_S<\sp zZƗZ""B)J @^9{2fӅT@[ߖW~wﻜTnjIΞ8|T"_G˴?~TڂfB/z Ft ,346e:sPKηtv9~;>XJL:~-B.y[ Bh9+Z.]<\I/~χh j+:{;O@(TG&$ђ!5<h(1F $mjs#çc5 Q&~k-7vSWwlwo<Χ@pHv<7\J42A[ vmX'ke8NE3zի+V˶t2D13O=L#ELFzJ%֬^}5:zUΕV3=;̡GxRVZu9BXfř ~Zζ+ӵqUL57t5i't!>G4ր4913=5d3ށg&']W$Lkη>}åjCGIl&:y$)tu=3q~O|Zrox[!4u  LLRCHr.oz~ݪtv -G'[fUg=]g3nCPHj}',K@t]/ |@!N@ Ȁ%h5-܋g'iClDQ Fpk0 u ՗uZX@ MM wN 8q,5hUT1.2 Riu]+=;J#o|ӟz_o=D%Z!hZLu"Z1DG0`@ٱS׾ A zbpc\͕hmMPUH"KtTp{/zv^3Bw{ X:ICNik{@`!Yk"JI `,p k噱O=%`V}W]׵eφlcrUb.$1vRJ!`05=L>#z[^_ܗHY"R]M9im{uKCgϟ M0$xCڛ{kzm֎#G\]u!G)t5=joVT'SԚMMZ g YZ-Eʄn6?dqHs}Zr)Tjy;;3NO !kA>vw=8|x|\[;1=}̙Ǐ=V[e8ѩrO=HOOgqvޫVܗqӆw p!eWƌLr\ǤRWAֺ4{sߕ{m2Ƹ[V>|(r&>?}H#0V5d~52QXwil(R*Ƌ T/u9wDd"C*)g B[*Wp%" Wnx7vlk_VI_O<PD̝x*BQFi,=^'ۖnIn/=O}vpx0Q8_}\}hf01\> G(`#홮]uUתrfz]4$V0kiT&\1Aa0,L]}Wo\UtP0d8̒A$.EQd u O2)Vtu?Scc30*H!뵺%C*KRJIYɉ#"$"48s20#ax,0Ɖ 秅MJEof wt{0GRqnCo'NAٷm>9x=}؉3G'y߆r51rX gfA d}U{M5l9ũƪJ<7>ѻa56z!m1v}}EklimQڨwu4lGgbРE H`5> #&c"TcS&Rprbԙ#8fDƂBKޖ5b?k }n:` Y6\\t"֕V|rX (fJ3] N0V `g?z[mo}[nNH*`r?TvݤxjxK;;0tr&--?{k~ݽ7!@-E`(&N岉RFu])$Yd__e:u8Ow5Z|ܹ5g3?G``,s2Vk&J\khy_aE185 R*6:#ezUx\kEZ@2nh2MҙhW-:iI߳xqqiv F42D[Hbꦛٲy]Z)$>tj/oɷسSX̾ 'etXWgާ+j|x#YG0`PXag8&2*[7¢;<O;w|4"%Alٲo~]>~{nھU=#i/xia#`l CGdX @b._x=V~J<>11=?ȖR`P180mn!k ;Vk-a԰ h%wcc(1\mKŗӏ@6҉Ztg0;_NdxxEQT==y7ܰo5W^t&j^Jy{쉁#NMy3y7;3\{bzbzvzw}ndvtbwEw``<Nj㘣 CQ~z+\}Wp `g>ٙYԎ\sd&  m8g P#-"" ئ#@c܇Mdib 8r rk LXL"!O B 0>R`c>"wwyme8?7W?HO_׾m,3CvM#`6H)K 4q`3̓owx`-3ItbbLG$:ar[$uMZRK 2u0N\,ORiˡq9CDeTGkjȸ0?3zQ\@ȭ^[;ޙnˬ޼j.kVR#MS" e酯" Cp|HMyOz׿<41[3(B@{Z4-}pkׯ1k.8aAQYgH &v2]9ڻPZ<:6']<{z ga)4GqZ)鸉50 lufG(3WKGlݺ9QtyU+{:A7[ ??+>qpi4r~#CÉ6I>'|=MOpuz;&Νﻪ6382XJ1K Ѱ-A{K_;,C.!]W'UI+~6a !g\c]p1(H7lADI=,O2S"2ST"À ٻbttOL|S=%\.ѭ|PK{}#\X 4k#Lqf6]cEoou߻?O~Sc+ӧwiKcˉ@-Y0Ƞg*ZMN=vvTKm|jj3*Du 4 8XAS<2d 4WwMc7nvUmm{okAέwR&YaHڄ3%b )ʇqlFPt_vI֫(8q2p|xdvz\ H'r[o{omWYͷGF)q)2rY= z3(EۦwS>3ӳ;no)<3s3lgKBQ9#l"^8}Rۏ1;[87V.TP8^U} )$ EbZ@X3֌ϔpM`uk;gg&3i?|ݾuL ;;AuW_sԴ#'ЁWojjzO=R lf͚o]wu7]}+jDI_ڄd1`gh`,X B4d](hA^l !?ЏKF~ŗ*Kd+(]Ԃtfnrs'ٳfPm,0@$\Yy[wm&x4pvpbbPZƦEh%)Ym )z$! #C`\ 6s7UɎ:seR^)gxW]g@WOV/z:VD1-;yzڵCC'hͪ5gO>:#~6=:1p:Ӓo08dAJOH q!ƙt]qLS.%D1H F$j&i1_ڈ4cbX@ )ƫ[pTi{D.OD3;z&lP[$ꆽ#ck=u4_?~o#b21 B`},T-:2C'Kѯ}ڸb}@F iD`il-<1W Ow]}SccNU``dsBm*UfLD-.)'#0s_.Fs!r,Nӎۜ-Ks@@sܦƿٔY,NdjO?ϳb-qڀ4x2Ik'g:rCfFcZgE9CGh;ut%tZ]'ĵ1!c@PN%pIR'ə9#_KXߵmU+{zz:V5m/^'x_ Oяˆc޵״GM\\ E_5d`@"/5mݝzLtN<537>}7Q&l핀,wRs$<VeU|;^_THLWR\mbjle$bG9sHClQ1Fd@x<͵]uw'R{np]ǓnJ%7Ł]Q^k,})T^\~Y^TOy!7*&ccD.ni+JgOZEDW^cϕ{L6hkm"=y^tǏ}I1"VS,\5DMʪ83x3K;N9yT&ֽ=_h\gGe;wkW/ a^gW۶qlxeeߵ׽K`c-$(O˜r.3O% o~m8Op DF@]g;$^3a^"u4 `uuEfBkg=R\_ߙ_Ѳel_$o>W7%\D ҚE<7t:16n^3;}c+ssm-`)K_zΜz#hJu~f0iIr9j cs'Q Gf9A-2pG+e&JsS_#I\v淾R/q{8rB>dϜ9W\y=w?CXH:sADVY}a5`P `M\s^,颻jh4puS\K wC !@: tDI,SK3$Dzw'??wwK2Z(T@tPڼeOH l\ N=stv⨴b-],72* rƵ6'р!]*:\n<` e\<*h ۮعk:<̴>0Qb<d,6 2v1?3:\J(TEGr}̩3a\ |+Q$95tU mmn7&IOCW_ktd7n[%!DKfR펛|ԑCaA&'w[C E˧eӎ#G'36 ==m<>?U2 dE޾swMW^-omV)}HTM0rI0!f ۣW圓Zk@p$Vf-Z󃃤c1VYHϿrru*K-ɱX:㘌-f &ޮko:Njw]W&p乔\qgCB{ǽ~ȾޡD0X{LZȘXH!`ڢ!=stMg:vt^ ұ7mYtj-[7jU_T~M٥MHqkՆI&ΛFI@!􂀆*H#i@dIb.NKUS3DDg_s "( !S8>ẞi{:фp&&O-@I!Z$ ʶ_%Uɼ3*G8,N7_&_(8"#׬ٺ.k&Kg-D T@&.ȌL]~Ū]_tbL3Ϭ_OU iP@FL`_HwBjYF82􃀕J G;l&Xa֭k}Or=X/.ͅndkc(ZKeYe-Yd-J"h'i P`,|k{pɢ"] i|bk>B%Xv<}t˿׿k(35J% rL[MI<31V]+8\7??Gz#}# BMD yI:?< 3KD|%6\0kEO[ ٹin?v陱T-Wgfۯw;w[JuZ6:ڪ.ׇz-$LH)4/}6BD7YI+uDs"P*L1 m\e,tS|+qn߾ydxgo6_,s=7rsOO55S7k+4`48NKP0ؒ;ũX$@K($"kF Tϓ25"/Cq/V,% @QDFqCbi vX0T6J ~9`8c25عzUWʗV]]+[V1Ɛ=oJg`LYq)'`ךъ?}L*[o˶;X}~*ۓpn\0GDUΠk&D?6rWJZ)8CzՖ(qRvǑH}{w1`wumٴ~]'0_ȸUdJŹVmo V b@^ H"8R]k׀GA_KU$*P2cްO}omشnˑg|׵zuϽZoʙX2*2!KH#.Fix5.K!9g AV$Isύ8/eFBB1n, c((|Lz-\moWcGOIիW^J)} 9ql R)?ͤ9O}w&&lRd®Yr>PtR0 l3rBBVc#B N+\z>K~^ &x>/GTa8;5ʵA`)GOCO^Vk%F6B>c7Hה:{f_keSRJ \&jRFʵ՞̲@L*3pjnu5V9/UEy8mhD"H]+kCZBW8EW7H롔 geȦ|oi鮇'4f$椨V*N0TsUWhUe$BOkJ)*ǂRH'zVvtq&ƇgҙtKD I"aԬS`"WO5ڻo>wfhͷ\?_yʯ-L6͟Qf% 30VU=(dcBڐRʨKCj HYc -Ȅpƚ8NdsG?o>`Z;I 5`S$g"LREOIC)Ň?+aX圢8_Nmܸo),$0jhw>_N c1d.lIDp c\݋XVfg2}+6d\ãc;,]$. <Жc'(i[Шy9jt=ZY$м=_TiVX62_+U+]+zD@%1 mY7] ` |d6;SVQ36O|G4E 6{;6ժQ됱 H)E([ q,MSEP)[?%G 0hX:ۼcexӒ=7aNjxT hoeˍ7;3=4m~e=Q7GR}ڨ^ʶwFLb_.,.2ȃ(֪o?y>S 3`i$)&82Y`yNaqE8:I>x5x{⸴c CtED57.@GKAzdQڳ-^ѳZ !1^=7\W2yoЦ] ([D%3JGbc#m&Qh.免`}$qretޛ.V+B5ו&|U" \)':B;d1 GkK-,0Bn~$)`Flh'Ѻwe:UgOx{DHZUKd׎-al2q&T8wQ9nB|5Bq8ayv~šNr="fLkz@3NV<%z7J d-MbÜo.Ei|[nB=ӻ2+;_wxheq[[wGGW͙#Oz^Rjf2geCD$ ljv x|_;ᣟ J4Yk .Bq2nC uAI3&|?';Nd3MǫtuI&k@1&E̤"/瑨,6P!ұr!֮60 =ϋsED 吖V#47-uv~S"$['0ȀQtƿfߕDĀX|& 6c@0CC@32I6tuHWrߕRBpmiB V'$j#!:LZp]8jotE /! T^#|\Z |hg'O ;"% Ѻ}m(*JւV,Rz Kb  bDZ*GIHilnV\3 ,z,pj_nj(᷾QsKz4Woo'dggv퓓+]Z-O3ʥә\&Wd(;lcmןٙG?{#E'p yF5 aɑj ")&M3d2,c s)H2&)L=L'>ޙ^w~toU ,xt 3WܑZc !!Θt$znXQB* hgj Dds!F/bQu"P4TƝZ->wsvlHE7[@c•{oOQb|1F"#ۤ]VSFǚ ֖5`􌥎\=:@(Cd#MT`w+yp(b!yqcŮ>ΝsB8q*}3`qt2n=7\CTZy^@vv!v] 5Ӗ|W0R dL}Z0֎ kT56*q|4%`)@K@`l`<%zugwԝqVní׮7=51;oؾ}ffgHq׽wֳS?LBxh5G?}Hi@Ap0_pFj֬۲e$&η=W>ӧϜrϞή٩uwu;R!1Xy#1wON[c5تӵjuD5jrinSomݳ}]I $Z ҩNE~Is=iАE$YXу}8XxB`52 IY8c<$*qǒ%D\zӧN :|왡i\N w̴6Cg0g6_?u|WGwޚ:ܩjq/5Gi/ԔԱb)m?[^'Փ5niHCxsqq)ʕ aRG8-ߥ,@[B+2?_ 3N0^=^rgX`hY>OvSٓG$ZDvx~u h# `x|q&1E|8G"ka'V1,bBc@Z'$!s!B"* 2`z0+ y`X dd`z- YKU-}?Ȯ s&$ ApO9 ? .^[=qV 8f7mwcugOwv.z6!M @;,b]o --X*ZZ'OtY#)!L{{޽{RrfQHBU*J#J,G8Y C񇺪DN1|=z5']%@QkZ'ƋSB}jBZ˥sӣ)`> @@|v㦍-qRe[]ǕC0I4.4g{i"k'_HDĆP([PDdqx*I6\VGI!эG1J_+Uga9+Mϥ(3ǑJ%-^\,)2>:)&%p"]kzt8RfIĒå5D@#׳bŪm۶}7yfpZ #x`a 4-G4R%3&u8SZ8~(Ǟxhpll"Ƙ`U:H=/r3B5Fh/t{Aѳ7{䑧r/0P-C [mr`εQS[TOƪB2Ai~dꮟnڸʚ1wg>wZ9"q<99A'Nr0C\-$ {%`!1Fp#k,'_Y<˒ϚsrXfjNzL[/ Hׅt'1>1EUOG??QEa0ɩ3gЮC\A t_h$v]87-@Bצ_뿑u1H)%}Oo~=`% 3cm50N/U/lOyPp$ZV|crLv0`VUQ&=Bqiyl>)?"uw8t;>R.[M`z67wrc+W(,s-YfEyEK_9cږMᳳzg ! ݹc5='<$h5sIJWE.<Z" @N*b.TPݬߝ:|howW.9w7'$)+_7[[3f\6WҶZY`3,%,WxSfga' M`QX!%4+ɘFNʒ,!rH#B dRцjZ-ˎfYqHE@d"roǞ|i`@ eH "b77O.ZpB1-5_H\~Z#zGQIȈ}ny8I٘g(5!K]D:jCV63t._f6I>}ݝw\{5-=d,̖k'''+ŨZ* O%Z[_n%{ߔI9MD24rR+s2RJ+X69z oF[kV\:F\B 01`:QdMǩ  ͲY!g]EXk8JWj6R, fʼngWA6jx~ԁb f s|q=v nب-[61FBXc!"u|DhC.h.ju# @M.4 8rjiT0,O< -"f]'=7?(~ƫ\w磌_'e[%C!X$5'6t.[ηy881zHT16`$l5Oץ<195U .9l0ɁscO 7d(ML':&[3PWլь!p&^_{~uCg'?O·U\N!-%QJJDDt\RĚ뺄\iU.UFI%ղ~z(0]n Z(J;40o|sͶ E>!$jBӲTRcZ׮VRմl޼AOǹ ҩju#;6mٽm˰񉱹}}=7 N*ɂaۙq-k|n-ΕʧO*wv "p˷I$-`ՐaQ9j'xo KI) `8&O/JRH0Juvukɘ] ώ k_]]F"ZKZsWV(d|>_nmWuLWxzwSb`pL}o,L-29Ln䩳g3ÓSlzn~&ߒٺuիcSl69%نâ2]Am%Xc -e'¥36"K-~Yد475ˁ!хz#)202.8/}~i5Jh5o劵z{:zz;VbՙXxւ `|Dq9_̛pf&!K+v][yaCAV;_zwC#!c,Cilu2N־+s'Y61S ֡芴D+];?t~kl[nE@Dg-nk35qaZh%"Z<-9Po_֭[\ש*L42<5|݋J&ڀ"6_pYQCK{mHm&M$Iv<ףjuFYttuL׎:RI[SO=l[VkpN&IK:=sgO܌2Jp2e泜+3_ڻ ! RI1nPe ;/l޵>~ w}eW0 t92 =3Q'QFs!kd|u01GJ;;WID@y;߶iݚG ;_iC1qNԠ^S'+\k["r97qYpIDYb_}/†˸k Dյk(9tpf'MEJk8BkefS1L&;7["< VZf`H˦EJH/c6ۜH5i)\z[ C~ƙrҎ i/7*#'$7dYU@ G|c/|iAK 2Z0AX Pbk#C zhhAוqoٴ9(2twO;+h@!̒ee3;&$ #+|>/x_eq^(ttT ($w@efK< @10d_yϼO=9069xa) f8c ^%2tvtbԙC`)ɰԝ5w86><66(o m [_n!צzWg8Rh7=D6MM{zJUgDHDF[dT*>{X>_B"U 9kM֬6̕\s\F+͑k:s7`, Aa|CzyXje's U~048K$ ־#Dz֍S!ĵ_A!"&Z~s) LhcD.gpdȐ€h8xT^\lذʫvidl6W@fHVYC\HQ) .P4˳]q26?'0ς{gΟOwPVK 8`G[ϵ⺾<% 0h@CY:|xeFo3tn[ǟOlhu绌1Ƙ5RGe!\l* o|㝮':81=A6B BH!JT8="d0z{u6a eLK^Cd/چ(3s h\>N(.q=LX$d\@3m6Z?.Ҋ4G|CzkkXgGpuxh6H{zTr(VNjjr~fr.* j'9]/ERC'![Z%ĥb2 ĩlՀ1O|$DvU{^.ٴa]K6]) cHDL`92Q aYc udDDg?n2o< i|KQ0rW_koޗnO&稳`ճ;8 /"Pb69|[?ggYd4DZ Z|mB97uulGwnO>i#1=G!wE9;><AUﶵ۷zssF~>#+ZU7 Qr9z:xnn~vn/ir!#DHT *fH .&d0Y$I؎:qqI{Jd S9 T\*Wd󹎔}}J\XZ~@ק'F'΢0ĀBnb[jF~ Z8qG豱iMJ ?:80>;7+~5iWp;wsYdޖͶ"#CI:哥,H!G\颾/ !5(0"dž Q)[\o|ZCTP. 2][F6r ^rˆsJ&CJ0I~Íy}Z F _s +.g fhRe?, 2΃ lD9 MT=#) WcMԟ|UWoݽ{^WE߷[s.DghBpgT.2R2$ ka*\immw=ϖ^ˍ߳u'_ϡ0%#$eHtcifizgR  O)u{{ǖ+ct%hucı_gji{_\ywγݱHqH-l"DZuI&v:ӯ 4dmҸu4^$eNj$ۤ,RmQ") w}g9\(R$mE?3`ys?FD8L=oPɼ%l͊,#U|%I-/o!SO!3pC5 1H$41gfby ѕwu3#cKd,j3/ſ7,XǕ+ 䁩};"F@c%l Ff_'gOǞVU A`sO/~3R\S\c`aL,]]zNY$*+sEU@'3ќx,R: (xdf`@0.61Gg_xx !7;_J,LBAjQ :|[y h[6xW6x/t×^:7t[cgZǶ*#T6s)8Z=9u ZL(mxb"{sq[h7ACpRV7uo8{%vyZYҺE3:UL(rk׻=bdB|<WL`L:o7핎,gDZ '@aB=oe3xx;_zgϜ`j(2#8ud)Ocn4jB!7*%VLo!٪Wվ5Be9~_}'?>54gۊ|gMچwGNTXvTlL^U4#.,w)!e%E(љ0U n6ڇz%ftTdDȄ{ 1I}qR@O~baaK%eYU^#ڋ'˿إ1DA2( GZ%!x@qB u(J2fXwW!W`t0x v]V4P"T||ўHJV{&JړsYfž=壇~^WL?{g~FZBp@[-Q7[ <-xCqdk~?yr%[{"ӣGDZQJ[M)I\wx DJ/~K_3Ic$"Z7>g["kv1ŝ#rᛲK#?'{W_PB"$.]_^y9qBx#cxOs.%|; xb%b0:EeSaSGZw|ısΜ{hO~#vc %ZW%R1xC@,*QE^e̕ @(P=yIRD1F%ۿ `inS}#2Ɛ\Y, Ui܈ZzJS3LzgsݍTk!+({xChB,Y)DA_DQ?oʼnɲ~gyCD\ɅzR, $@aZ6YPcTќ OIk+P4i+We_Jڛ{{N| j4ss?}my `08}4"jdVgR &&zJ8VE15{ܹR%mi# ) @ql$FB.COTچs>Mc!$>P22Q1x=*@鿱Fw TB YPV.v{nq5Itl} `iū7+FrpF-JwmQU`Jh\it?6~peW ~O )GmDE?;ӟ\JDZH{_|3jGW]:P5h%ǚԺJ@  Lb=83 `&JbSq=ƒh)MӲ,GQd_`8Η"ouޥ八ݮ̄(BRj`bD&6PVruY 3&iR_ȤVUvsc:-\IDU/հYy7xV[ |ӭ~>Cs&ocR>T P 50+Pkk@7SP4 Lzmd;pPx?0wkb`1 `ş~?/qYLH_|ɓr)\IDʊь>3gPޛMFBUFQZ p!^o%Ru |O%#" >Oy^+c``lpR|;)x"=0uxVFb*.1 NNg2x<U5xmck46HBmP!Fwb# tjofd`, XUʘ(Ic!Ҡ;vpՊ"9l5 B%xXخ ٷ)O⹗rۤ?$/Qo&X$a`fl#L/_|…(rRkO7Dɸ,@IEĈ$IH"jbR:cEqA3‘# 3d0m䠪Ԇ PD=*( :#cD(] N)j0)v]00!qpq;WJyER5[-3kz6ʊ~ohmzYJ01X)U׿ꫛk_ʗCFHძ^w^ayRbE~3l6&Wz|\][O_ETe(=@|AR!"vuk_JP#(bZ>[O}h6`hx3M$o{ QY•럘e3QI9uI ,:d,BPyOB*5 ӕJ)[V6wa8 jV+rAdf^]__;#`uLyVfYW%!4>ȀJ*!63iuxw\Z-D `URjE:2W^ @BLNM0E2F)t~:f"^{|r!D_iLs0pʩ#PIci*Txvhj 4/͗8 [T,pϷEw  o v7366B`8RZDѨp}Sǒ$Fe<Z.\|cʂDQԞk&fU]HBHxbz.h?19ŕiԛxR$mҖ teXIY L\Ŕ[iJU9~f,uC &2bJV݅uBRbVFمƓg 24 /[>MLMFP0G"u`Vz7v߭ƺL9fr9f!2N"D 3f:!OkA8P0l۵o ~-&: ؘ|Saӝ}ɉ0 ?xG玮nt7{KKzQ6*QٔL_v@^P[=|/oV|]](V__Z J(ǏG?~g;87U@yY~5 GuW6{v`ADhwIEb;81wo3@(|?_V-N;`ܱF b+mfcko\~c4(VqYU4J-A}3ffQ x'HBfBRL! E%Q;YZH%Ȃ՘pޛʝ?އCWP*/Js.c{x镟,*D.V+#8j BTkz2gUm]vLl.x˩"@UnkzP^<91Z @`q;,F8i4;+kn!ZfpԨVpKmGŒ%@+#TפneoAb!q{ilv"jI?tYD]A+&ʢVF;M" N{n.n:t҂Վ=wCݿ]KU#7.OOKƟSkg A@YfW [.//;v ݕی" D[VRVK(^<.wy պ "`m%@&(F,%q"àŤ(1$UUuk;WuAH)Z^@Qi|eji,\7[k$n{[#Q\$cDJ(+նe+- ^SqSwdn!{f6wz 3v %6zS3eזօP!2e~oEq3YWt*( <ٲviq(of|ޓF_>d)|U(#U)Rvsj3dxQE.M@vVO.y}:5g @*/"tѼ`T(XVWӬ}xq_5Dԩ?>{ (ʲ,KTeQmA 'GcEPR[y4 կ]x1.fn|%$Hj6ʆE>@A0{栴&#@4Q,En7uJ Ԩ2D2RR}FpʨU93;I-;3./.FQ475f]U6VkR-μxj]!{&  @5w5`a&@(C y a@DBzseh/*@jHZG'?~ɩ+o;^Ek$^_ZiHʊ+4]ȷBىNH}W^=~T y+%@H>8gΞ={m H Ѩ;vbffި9W\|i}cu# VO4g!!Л:Pv"-((jI_??Ke^?S>t0YPs.9 & 66FV<jiƑ߫*sȳ2/q)ʪ++vL"`c&oL$,2Z-y+% XEFw~fߤQI 7:B^--%V*'#w~!hmm#Sśrf~fGdP GN<$Lkw7fYBԙ8A7_y\ϿT%e,(cwex"MkWWׂ:ըՒXEjk3<Է{\@"ԏ{>l-E:#. CP䣟ϼ*$"BF._zcQGf #|Ǟγg0 DȮ^[yo(NɊ>}41K6/;7\$H!02E(""CGDZ6cfuxc >ZEemYS_ה<~K}#Vq|vۇ"χz\6vg3+K}3mX*Ȕٰ0"ɆF^k;(0xȈBE$w( QgcLJ;LI.3ONNI.-LNN4 fS'OILy {"x?3Ӱ%2W¥WdEJ+ 뽾'eT/^To4 !ʒZoЏҤ35QoR+>+.;zlp@T16(8'yvX:_3#l<~_{}glƲ1 4!yRKnVZyL{GZ;3OSUySG>'k#֊X].<> P[̌2 }0&#Fv* `4L΃M݈)2)Gg1?&h9_;q;1v kϓdfSjvO@cdv>ih܈vDd2L&d.d2L&\?6L&d2̅ "If|Tb"ܲ+/Cl\gL(o+ox۸5i:ߕsW$m͔ d2L&pț@L&d2ǶhFLl8G8Q]޻qN;7ivi5us}Vˤ&K{XD "|p ;NJN={\@8IiL383|lF&Mf=M8 ^09}cOqrӞ5g~6~G[4fN(N씎7X7')B4vDҤq:<6Ζ)t6i 8rcMfڤzm/m`Z &Cx|##_N6opLpjek9aOqki}~3&/b/n 9mvl<ٔarC?M>'lfK5!d2L&d."\qd2L&d."`8n$ئS~u NJ1e7$d2Lf`jc#D3bY6]W`6tÿw㢬Gg2L&d2Ld2L&dpЈ_n?+ekQ]ɪO)_tg/m: [;Ievyrne;6Wcv@ b)\sK}dZ8`\Ф~ 3~73K3) n|1M0N_|~:Bg[w1fӸK:a5ej:m|ͭAZa; ??vhW-e'ۆ ]i.d6}6ffHo~ulddOknZ)MlRk0j0ppqu5;-Fb*hk&-77qΦ ۟`a:Os2L&d2d2L&d2`S3L&d2md2L&d2d2L&d29(hU¶'MǶli.Wi[Mө)G9puuΪmG~-8ηmuzϴ8 hf]IްM M pޘN}6{'ki)tO´A&oO+f2nN.Vqĺ[d_]6Ι;MۉzLNѴ޶WLlv}3/AmJw, nQ ^oӝ&ЇU6'U7y \6lz?b= >2N i ]ՅbhlilBL&d2L"";L&d2LK?d2L& 7A&d2L&L&d2L&gtG:,4F:ۦ6Mܠ)Vm?"I/ulOYo1l t>ڂ=2{iuv:WiL1lPbKJszlMsxb鑍@s26~lMׅ\= ?LC;؆yvfu|< } Ml)}/?iAO/%lQyv2;t9]ߘ;yi'܏ݒE&-k\fM&=u{pC~11H f d2L&@&d2L&L&d2L&L&d2L&L&d2L&ydAyZɺ6rYיx06~2=﵍>kc$ipz6ڜf8劧2#w.Ӊ !֚EQ`dkn@Cӌc.؞]&܏c2fM$l{.< ]IiKW3P!/ ) "RtjoP.z8!d6Ű8Mld d.p̌E( ͯ9 ;D٭d2d2;3 DpE#P!if2Lv2L&1 䜀D4v)+D? gd2d2h@Xԏ'(A Fs~!"M8os#3L&;L&9SO9N0|d2\8\g}tWL{'~~)7ih/'4Pmyb_jn7uOrr&ɜ0V3S7nHWDӰ;noٴswMH:Ct4I)J_T͔El;b"U8 ĜI354I˳p4U&)<59Ī*oeW`bx}~K&s94gI mNh)y>mE&.=6Ko;e6mXoXz`23KY'Xn͈3?2 002sK0҆MXęj`t].pj*)̓v6߰gf*wtqt&F-~ٺ[foM~d`m4O.ioMPOs+a|>͡7H قsZ:vp3M4d2L&\Dd d2L&@&l 33#fzbH@L&d d2L&d d2L&d`ɺqFoH U6FRSla{jNV! UNi^)6'c0IAbg0b:մZ;pU/F^,aIeųMՅݶF)s:6YLGZ3_K6Zޓ "ndɶ$٨FS0rCE<.2M6tU% z LܺbYfҙ؂7[;1 g>^&/;lx6ھaԀěs55j.lyz׫6x&X'/;9_j.m\d2Y ͟hlf2LfL&9{ *cd2d2̆0 ,v&TI$BJ fd"f92d2d2s |э{wX]:&0ʪ5w!d2d2N`3Iϰc+JI׉WHL&d 9_ژ۔sbi u_'JgهSl` E9;3O90Kii9]DŽVͦ딌ցhTs:YxaѠzQ?ΊLuZiɽ%:49'O[g[giz7 چ&L lfnh ~mҁ&wܯB "0u. f3Ȉפsm3glnu9;0-M߼6.yrd''mUF'oE4Ff(!mΒ<mZ[6^@iZ7_,Z4Fy&y|d2[mRx5LUGYRźoP^P3L&@&ɜ5̘5 K"ڏ2 L&d d.D`,t;~ѺL&d d2XսT#(Q2g(i©R;L&d dv,hckg5d2d2c ]x[``6+SK̮}fK/?ifG jT/~o7khmzj zeǠ vҼ9pOE%ͷm J!i]gqO[L4iKW{0q5?m[x@nD@ 5Q@mcq`oSkkFܻA5&lL>jP$7iw/MΫ'Ξ%Coe 3ުFFn9'ن t,@DlǼ^=37XsLcYpۮ1vāggIƍ$;L&lO$Q-&$5d2Gv2L& k~]QU]:F|dd.jLIY,i8II00"BfPM"We$ۋKߞϦrBV<3Lv2L&`@x0Hb=L&L&d.0ȈU`PR3D%>L&;ۑ:v<*w9mSDFF%2;IlhMw[ɗ:W} IXdn`h-*::ٿ D].1?M< gZ]dNYǷbeޞ3PpuF˹8QoN`cF6w=:*Ul0rAĵ@րh0cQ2粓:V[ey=1els"*d&Ȩ FGdFdܳ@aUf d`c1& }L][:DTUD='O0^o7lc' VS磬Ru^1bkn-lCch3hL.9=3 23"fvR"M7CAhozCh3sloR1핈>@6JS }ߥ_^Ql[(6گ!#2tshChCۂǦLLfO9B R\L@نk&R DJ\ɐ Iqӟdv>dvE)+`!s9_jbE__6*['jILe;'0"Pg2d2".XM9Ȍs) 3Ř}=@!3)3[@?쥡2D͚uFL@RjTR3b8t򳛙M}Fz1AѿMbP3wob"жu힉̒lZD @&*ӢQZDɁ $\J>:ƌS$"ffaM~abj춓*A& d?"j Cg^Sz?lݟ+,[B'<莖 FRru-]k1 \s.㉔:^x&X&Qm/ۜѰam9 f7Qv?1 ,4!F=R &fQfgSl0Ft'kBqK@5:r^|IםM|Z᪎68HFS3oˏLZ@Ns oMmsŘn}˯xق%3VOO֓H ʙbRi+A^pKA+cwsRс^$1Ba߽Mֶ|Z,LuVEnAD",N(Đb"P<ղ,(cJ޻/}L8. C]$*ES9ӆ`)F2H uEbdH<"!B\Q(D`9S#?n A!f*ۭYءI 03=I{7NMG On4a<<39[9OSHrVx֨E2 40@Xbf4@hsi_3He`kqb|9:L>YH \$M5Q2n3 h8\?&|Zz1/uhvBL,z.ȗ>"|Y:bJ`3,S+ۭ"%Y ,٪*B0SS ;),Ƽ" @.^à4 ԚlR*i"jZ]fgf;VR~U";_juE:ffhP u r\ZU83K)E@JQXlttkqum:4i L$%=2C](g,\b(bpN d#*'d|Cײ- *dvJd`E)(zFHH8\w(LEPՔb >Rb!=  /5,$pf,$D,d S$ R"QSX6RB"LSJHQMYSJs1EqfBVK⪳ "K!P30iR-[;A! 1Z)Bpї uח8'"!D&,TQC?!0@F4)P6|4neG8 )(&Pi D"{Ա im2K&@&-FՅL@p^` c $Vb/pq9'mк?yJJfd`ZiTk͔I5jZV˒43dI,jteN _&rރlq#&W󜒲B TWWp!&%jD\Q@XZkf?"RYz*Zm&v,b 1Enϴ,UjV8b7:BMiLAyB:+f&yRJ!‰9)<4:0靖>ٌo n~ @)v9ت- A d lof+Ҵ@2k\9Ɯ"HSgbK #qm&=m߆'jX1B#`f"Sf1PMUU1sBDɒʅ9KK,zu%!A&3n,(w)4Q{a6f1HّIS5P I#@ VZ`Sm%Hv MPaOIAE+`ԑŤ,!'T53X4yձ4ŦƜ(εqi¾(&T5a1kc)Ƙz΄=@g(,6]BXMB^&U3JNC*XrL48{ gfўڳ fJrh,uÌ(f&y~dd6 jU9E\ 8̔B\؆\?P3' $DBs^n.R"NfieE c-YRJ\vhR( e!DXD<9v:DT%J'ucmn5r/2: ըt%H`|Y`f8ei,|q_\B`M5 &N`jԉ4e,,kJ=PI5w4&1Ab42H/8&IP:ŔR󨃥HB58蕿fVCڔ(3M< Fe[ԧwz0H @8HE+uQdd6)FP2_J4K(ƮY5*MqQf M m ) Cl~H1k Ӛ{"93E,I#PҒ0 R(KJTSTw@jvEQcLA*%&P>v{FDdek$qOixm4Z@ zF?{6֗4$NzM9cg"')Nf$^a(AR4q,`ɱ%( K4E##!:5*IL,IJԔs-S 5$dfM{1upQ*MZ)V 5:N j  CZ+j:o&@f Q M@c$ !0[&E+hE R ]}sHV6MWON}NmI I;1&Q1vLZX@R@fRo/9&5%a)=T^UU]R%1TT!{H[m4=1X-F033bEa<]Q}ʬ)`֋ Ta(3k ,yNa bDFДRiRbS31Ʉ( މw)1YӉ"L}ӔzX<9R'Nbh4™\:踭gBľk$.ARy4pV>Mdwe3:3j'9掚hSdܡ6}NtMӪcff8 ghB XOv'lfFZ=7'1cwnjlk޹NـY%6W3Kkv5&m[1"*ʒ`A [m`9orK(ZD&r=M}11 H CmYJ\-?2eɱi/j,Mg?V iQB "&&vԯ\}w(A%fZ][q F5!:ԗjR{*$MYH5qbX#YvU z)$9ꠦpL j0b& qӹzUK=o'b0kP$RVH5 Q-jP~<&)Й>oX\4d ɜhu\]2("mrAٚ$|O$uķʪ꒐V5kAJHj&h¬1RJ5! "C{JbCUg0b =;} o~d4isbDMFi3 tx ՞yDJ05/<"ա5;cL@&vNCB-9B]/@j@$,LbԄ+`tC r?4t*@u)жT1,f2c@&s bU/ "v 0,Yj ʎ:VuhlIJ49.!,NTL89qQjPZbFRlsς\gN!Дnb㝘M:#L%M v$+l =\saFDĄ,/V B+3o8DS$"=1IUM0 d0^&WpZƦZ̈0jY {L&;LfrLm`)IgmKD_61#lLvN )<4pXE] Ks`hL۠d'*0KmJ⼸i Z'cMe*:;Y5Ɲ:u  ,C쐁m 25?tꀡ&zS'Tw dpM"⚶L0Ue8Y]fjTTVhBXU!EQK@3hvs.4h4-h@Rea kKϠ~š״ 9emYo!@&6G%S49o>nbtJ?( BBU睔%^tQ`4 #FPf=̞;0ޠ#Bg}u:OM1ehCvܷ 胙iu{* #Sm!I#fĉ7x=P);iH)+|h*HF2ZtӅ5ha%QT"5R܆7R bwd2d2'[Xja~(LSQ5}:W!V Sajk78rRd 0mj!{6xC!S~K7Õ:c&CIѹmObhtp Ф*) Z0U @NӔd MQyXJuLqfvλn;;7Wub,,z!YRM0![΃(=9IZJbt%"g곖#h0@FN2EmdhH-{(A Z_ քv۠TԳmW ~BD1{Y,֘I!{0ڡc(Z B&|ʹo>b@';e)D6M{U6i}?MӕQ;6N=u95#7~.hMUU;a Ge)%epvqHVR 1x ͚iYcޯK6f/Rl.45y"V瘒p)׳^v&y~n,33۳a֟{Y9hHaCc|)Kvq*%b1+0/~ Ѭ- 鉎Zg!l{:TnE5YR-Ty,PFGZLSJRBEkF cX.-'_x{v,Ldf J#-OHd…&36wcÑPϰOvJ`mLViUH6S' !aV#3EO/D%!'^5ҵ-'3n/Ux&(H9ue,8vLT 5aPSSF"LabAWI mv\h=M_j"!CZcO6^wFtu2 O٪##Wz;@}#cc%6-aP0-Pj^5)m'ZF F6u^DN TU,B€BȨ `ʎILj֘b`RMIllJgYyvwKI?؆Fm>+[ &;IA`a 2Vˢ3{DMQ!gnHġ`׼ft[h-[=hmt5#0h潵1ܡ*ZLl!QE3Ӑs2;hC\85 L0aڿl~$bך'?j*KEN5_91h^,&~|,cVc,6sR `0q7&PMLDUc̅p dаF? mnjJ5I1bJNWYb]wUhw)T-p^<[ح1596XL&EdiU4-qڭ, gZ3kdlbR Ȇ*)1 #E5I-Lkj(5Q:qP5>ZFXyVomgivui=Sm4^;UMUweRbk5mK&К]jfzҠޮ)3Lv2F0 {)TjUW]؅eƖMQ$B(&ǔ0Q]D\>jRSQx5 uM _zyŪ"&*7JMLD$e!#!pj&Worz2|ј \& yOIL8m왁bLS̉6{ig\Y6v|Z^\iZ]ИZBND֚iȪSݷU.13ùrNU%NB!0q_uB{<#2y&k~Mg43w2 E|s4{mu{ Y_culsƕhRj7,U'#DSN)>nӔ45 x󞝘Yg2MD ! .Q0QԷZc+19JQ67%X`Ebfudq#~y.W!G:ЯyYz}M}[6TE`W=!K#*Cl5ڤkPfoo57~ 6c&G2#䨘YafĂT]Q@S$e@DRDf%+7M\P U%Db"嵦AR!uaaLP:jMYun`FCko~P"7ȿPɜx[o.Fg:!ӄok46)fkhD9OJP11h)Ġ& ·30CL'Y$Hn25Fu;]SyZĆF ryz67{t6+t8wM^, b3NYbgfaffشIL0#0)v4\yʑLyuÞ3ʰd2?LˆTFS*gg!KɗޗIJ x̘ݺP9Xbf X ");) |FPZ77F޷WZL .Z%5aCD[5цoiqwRV^&Mv2 qicIbfb&&MkI)&J@+%sRlɗWAYxm1X XUQ3 .f?Ψdˬے%w.R* 2"O{J &F¦TYQLd̜䄒 N@dOg2Lv2 5f}ꤩ)]Rlx}fvNbLf¾(#;b4S@R8A،̸pubt#Ծ,;}b&2&.D~`e"A/۝ J%53vRWUQM~k)<@pRBLj`Wv x_Zb4M g%"0 "&K8Ie|'cx˧?G~؉˯ƛo9t%\Ҟm./n]^-}ǻR{ۿC}?KC-C2p> ">׉_wK-uVMxr L@5W͠l\/4ti?qFyc3 5o/K=fKj+& Ե0X1DmAj7kjk@DF!LӘ3ḒMTl);<9ۘ $4mc޾)+ XQMwn}cw?gg|~C_S;zdf{so-RLVz֭V+VujPh" `"6d+= Hkg#ުP6KOaȆyP&qHA$I,-ۻ{_w޶k={.<|/>[!>xz)eOH[XN&32C< 驧?*}zoGw޹b߮X-ALӈ*__`X}ाgFn ?h; d.4l Ǐ0 %Y rOԫuL{+_:򢗄r D!ZJu؄odc-k"5?/gegؠL/N A",-YꬆKP\|ǻ/_8|س4;\ i '+Z:*\]4;2@tvjفK_}cKZ4bHM;'M8W04֡0Kd2;d2; 2Ӥju^W=3OPPHRz\jͿ+oOo/y?o7URUeBZfo RZܙ3춾oC)0nsUK_xw7cjX^L<_H%|YP3_RRT8cҔToj 5HƢ/4CAԘf[}%/~"@Ձ΋?LSTUHL &V5Uf,bːш~BdľQ8g[v~e[PzєW:GV: |њ.&F kGw fJc(o,3) ңzx)Tҫl5O~fa~Osw/?6EỶnCQLT@ ֩M+mi3!ykJ_jLvdfwqUW]/?Q Q2|4ӞY*Khrß]XX+/]ga#5R @ٖ'dȑֱs&&<33b̪*N,Ԙ&L#=s'f2d2`"P#YLHh^{->,qP2R\Yez[.3헼V])V)%_:gykVՊvm?ϕL?ЗEP"b&!8"AkjU _HadK#s~>:w>^z%.y;^x}n{ U`~\~Kv?կtuwO~y.M?7L$95Ŕ6 Lȁw]r>}ࣚݞHTzx3Q$PS܍`2du JgffVWڻz_iqwhl*^o;y>7~_}-3_~eW^y/7[~ŧv͐IYuH^|]=\wIe]e&3Gscx|-^L=^`b `V `Ȍ,e2d7UahK8æ z1rMx߁NEK fdYH(*Mxi꿯wWzV KÜ #Ħyƫ)rF4Z#o+Y韽~S~Kۻ˵ys%®%.ĩ33Sc']yGmϻkop?k5l_{hx^-/޳{Oծnձ׼|Ғ~5jǽo9-Mwܴg?U7_o-&B֟o?exǍ. 0^a a.ZW dXGuyw66 D|/]E!&533a no.}-ɜם@f'f 6:u/EJY&_n^:LMU#1.}h n ^f/t\zWkkWnwtc7 3jDJ6Cgw=_p˞]V]f1 F !֘:kg+k(flrll^L'1 e&_BZgܗWgg_g#,w-VVYl6; )̗.JŔ#tkJ7zЛ~'onk/ҋo{ʹϝxfᦽ,NBvU7Ui5vBQ9S" Fソz w_y{>}_$(]or%{w]9* s!k5 zdk)(Ùt:6&:`>oY%֕H 3؝tQi"9%ủc kj|#gI7Ԛ(YkFRa VQ̌2?@ljΞ_ ~GfI̼3`K'ڻG!ȹ5\,lx_ʱ~a'"nLy=q87k&V܉CI][]\ >gz[z'M4saJӉgMPD3,t uVY (K(=?2*08ܷ)*9 :9-ֵjvEC:>e_;g%Z+G>y%wadg$6=5Nt.خ;;7btt&T$)RiZ'V`,N߃:54?$XV=\pvLw65 UEg]g9{gr՗cl ߔZS^{{{1%U3SL^cϼ/*s i2oAM-'& fbb@, Ls6H&@&3 qxo=#=SKuUGvG_'7zHզ|OT ix `_ &zUUbLqݎ ջ}OI]1,?O|s)薗݆R \󯚍 0SZlI߂62BY/ه<ޕ%kkzDŽhk< -..7&w]aZi<ܳ[nzpnav%{oQե1cbUe.&&%$2R0m( [1 h0 eV]]PsMDZ\|uE˯E{$GUA՛hfC&SB0/&fdИLwHKKI*쌴9YhJg @&3E "؎ ?2VCMN4RŮ]u{EJKwynVӪjEdDCj!  ؓ&pMcj;8W^z|R=Ps+7_~K~٘S9kO qf~WW];VBuZms H>GmE1\ #ӰLD&Aڀ":hc߿#]ZzB<6řQc& ̔RTD}W\:?:t׼U:=ॗ_WaEekL;xbw 0cR6cRvRBY(fi-DC<Өk3L L LQҽ{^:a^'+y+@ $4I;gMf, @N}Ij#?/áC1 מ@BԜ餐R1Q=TMhBBbaN8dl[q9Vau$ #gj#?{'>pqXR)5ۦ`"&"Phm23DQnT Zbі_~nlU`~'~;t/֟}ϻౣ':ħ?ɽ.fN{&vF$4[&X~'?#޵̽}PJGՄ#1ŤcR( "m;ֽk?W_œO=;^tנp7z# E"NKܜ)v>^-s{GJMpA,YQa4dpouxXb'w-?䋲[wac}5-`pNfJ#ABYva"b23+ fcr"DDL$jԨƚJ5ALr`Lv2APos.xĞJs|eIP}[~ߥ͊U7Kj'$l 8)E`L fI 3[;~gJifn1~6Ń uUW~敯|-[{ZL3sdܱ%^'?puW{<1L{Sj#j@H clytx~k?{NA-(]Ʌ$EMqH047;?#W\qW\߿*S\[]Ä:*vn;^ζٗ:ˮ, OF܄]4>ִlPa8wMϿ5**[2?eRh hswM1h0q2;4jȅ3mLvrɜ5dE;yXmnNzYh`umyK_'?|)i"۷/?˿k/yM?Wx˭W$lФAXj@M"Es@( ((JAhnn՘ +ˏ姿v0}?sK._]Tv}y*BtnQmeI6S"&ւ+_~ _ٿCÏ^ !%hd.755]W|Uv-gy)h%瘅;K\ %etDj6Lc:U#jkfYrx}^WuMS?zd!66l2l*uE~/j5=GS|?#"( ىsD  ba5c'PdjԒ8T0fUF!;TP#Yɚ{ofB%1_·>}++JLG9ܴjb*Q v [㏞xr.(xϖ.҈q :VⒿ7~>Oʗ>D'2(hPuD$Y!M L`Eu,gbi"Tuc("*^xǝ/}ۮ{GNt.z)uk !.fۉL {#ҦR:= 3V+m _NNB f<֛ݭg)u\ػ݈[.p̌ f!ILDDHt- ~>L&;f*'̜Dl֐4j U + Vr˩7Mv~w=_{udNLe$+Ru*RyϽW߮ZyFjĜPRR*;/FGXHIJih L&;űr#ҏiL,Sr]oy׾TV٪ժkM =?jU v-/PO?ute9R?WA m{FLl5\.>ODwվjœT˅d ϐRZDX)JW[ Ͽ,ؚӁ#WYZ!uz_N=8N/IU/g}ݻ?s_袳|/+Tfv|˞wNLk*kƭVNHžΡcGO]zeՇ?7]/[09RO&\/±{{yُ쳞]w#/u+BEOrkd&im֛|ö1b_4So<}e8BQ'Uk8%,]?wuWBWo :zp`VU / #pwҞlƯI?6{[/kw@P,$au%V^<`=X 5ZCȵ4/UU[s3J?c!0H9Gs~'E&}GO8!u,C'T'XWjlejϿ\\2▓Bv{_sUW\{.K`l "\S%bWڳ/뮏|ⳍO<}??jJ:򑍉뭸'4V#PAVᇞ;?sO~õ8BAR^Q( \vknKڴ]߁/}yeW\vPX٤,%x;zS6ff++t?_vÞZzӄ3VcNL 6:zm b^9S@IA->^ 9U23Ƙĉ@H8TuS/wcM딛ГyobrC<8dd0mv1%MjZ[fc qTRtʍ$ʶGzvIɜ&{x0CΓR+n{5O~Btcnr{9rÇ_җ oGɜT:+16(Caֳɘ *rUF9wͽU Aw~]/G>^Yc‰?}[~ VD%{ K5];cO(ZEkeyUzmF=kߞ{ww_{¡Cz[_0Rq_Spru]w߬ffPhLfV&GHgeO"P 6!S6X uL`V"sO|/#LwZ\eh&,BFSDASE޾^?7/RqTrT[WL9ERHx+`~u:)N8MjvB嬰K]wSbB3>b}ԳiR‰Gtgw徯~髷y<+|ﴫQ5 -,' 19]]ڭy 8uwW^  s0ajHQSm,t;rei!CG3]yu<ؑc-\D1M`*PEۧӧQ&оrZ4)4 3umugfD1:Slj)Aa<$d s0йZ?AwM)df)j*c]2XHf>whee؈K/W#=//(|qıEQ\5<7`D `oؿh3Y/Z??o~ btԔr'AgG3'±zEo/<S-LZBP;_$>uTe[2tZ,&44UD%R 띿w=Ǐ$FiJ"]LA5F Z>~-'o `1PZ1E,jPiNa?Nx΢s{GX=My/US—Up\Z+++aO<|KP>[E,ł[(-X~ġ{>} 7_~uֱ2!S. USJx%{(e;R+8Cg# 59b)bHJTze{.=vÇVnʲ\]]9x>[+UNg5pM<ϻqVG.J` )B hZh@N8h|34tg"9ɯ}}{IqKD#4ZJP'0]wK3ۭ>g YOjxo<#>z%.C8ԁga(< J-=}:z8@w31vUYuԪʢ*--clm?#a.R0ܕ@ PX-ρ K{Yd_׾xáJF sAUP9q%{>j^?_my7G~чo}ݫWVWw ׭.-̬0 ld1X'$éYrv7bT'0ZC@ DuU[&EF @L0'[)Hh&Lf:Y찲ZSI$53bn"PKu->yc,ZEq-issJ97{|e醛oc_~TJulj ƒ8UDc1v}{"[C|7B>%pD p?g讗}Ń]473M*RLɾ%X{;FvROm~-C55=ҭ+,/}zO~ݳ|͹4*jι@={V%{/S_O{Eb<#r4:X GūW?tV≣>=xpۚ01gQ[K+kCO< wWܻgO?ze\}KvV>ڭ7n{=sϾ ߵkeP3H R (4b(HoqFF?L4̬6ޜLqBd s[&p`Uw:Y Sm&2hJf!Hj&މxPK_yDqn"W[^?nF82̮ˮb׾i"M {zٽWY $`gǾןٻ+JR;]}%Xfm{bs^}5{/w5%v{AlP~~NG捯L afMsm ZE ԍǏox >1BrϮժ]yū%w/}ogWjW/an}T)7gTv†Xqxe4==}GQVEQmp9Au&dpQ8Ca /|/MC@))%HrYЏc.۽Hބ/!x˼\8` |_j^ ?^u".יD=50ٙYXNՌj MXx;@KS'ME"IXJ 9w1!#hmB?C4 ʹex*t8_~ͩlϊ_9@ES_Wz\Dd\tP:]9;9Ad8"A!|\_˯9;q(PKt͚ѱ++DYNIkgypU[W6D#]v36y/ "hlվk7ļ|(ڬeH'c*H9B?卛Ǧ>8D<ˤ4lW ̴GK 2n ̙;^`@v1v:fZfA#)f sX˕=맟xB8Ѝ8n4D#;x\ixo\[>5|f݇Oy367=˺{I)H!k_^36'@LLL=?r"g]5"}S|قL؉-溗Yj#9+d珞ݽ5)|g}Qj$қO9 RN}+:Z:QJ|>+y֘4Ms d`= IqVbf9fDI=8nO7 gtlr,7VoƱ+p}wXljzSW`͘bqbaڨ+_i*1^jZ+dk3ggٌ݂BV$\* \mƋ!3xFvJ"sL̎β͒[xF<c,>m{x1r#/1=lm9R"[G%)"r#3;G$qBJ@Rs3shmDw|߂RLL[L(:X>p?{`JܳݍU~)"#G-@DP d ,vzWƨ mR=XaFHM˳Ħޡ?79YjrC}ooF桥=06ՈYr—7?c_B wD /?g~1\>h#NHD2NjZVm̭rW\lRH(vȓމe~{[>]B bD^O?tu>Y>1?|lߞ7jC':C옐s DOK皁K; 2(a)_ZRj]ڱS)S4H MO4Zxq# |XO% 5]S*=]K$+ЌDJ( !jqk*9`q $$ b~DƮwkfn>W(XGָ_j-<~mI[KW7߼}~n7L4"?>>322<<\*`fzƗE1iP:f}N i<ϘTAP"X㜳`zgϦ:heL>wjG_qcbKihP ˭sJ@|/6R3Ƞ gM*N:qNU st(?[TaE/y2!}~w[y_(,3"fIHBkIP M4D!^rZrpy\1.\#B) !$P P [(`^zuj2Xqz/lufjS  ;n{呗Ξ8ZYT0ߘNRVp*_4o<6ZⴞV,8O~/CssĔm)%:yH7wzW:ѕCH=$oI}kՒu+WzܐFג6#'X W[B9JFv]\K |}۸tʛϧ੼w)Hi:3#^xonݺ0 !@` ΁!8@0\$F[r!Xj[Vw~?R9$F$ D` D) 4Rxq9<x)pvM[,):ScK{Zʋ/$N Ủwepe-ϺFun~f"qC9sm񳧟Gpy?;;{Eݴ0f? f'fo^ho>z)SmۛU! (%2#Oɟ~}c'^~iNu1?_Gp B,%s6mr? GǼV`wdM!vm-% ௹z 8h,]ggggFGFwշ{+?- GIٳgl|\i[{{uSGqVcPbjR_z}# {1 n~HZ[cJ"9?g=?^fJʭٹuU*+{W$^9088%1E+?OkػEDwYg&k3@@v(gӊ.K}^{5$+k.M$nGTQJ |i2EP0R}n~�R,T[x"\~`[7`NO ڣRI+˜fl4̗_lϟ;tpȫ[=?3ʖ SA nj !%/Rg`gɲp@ "*ϓpE \.rEZH 6 )r` VJ=BC|f˖ÓAw:xOл{S9:%Tnrtx7RK(89K=7owsXaIVgQc@ ;)9N@6ܱgǾu+6>#G I""$Uuڵ}[7omϞ9}zbtk tDv~α0Dю^>;uZ~z5ȋם ?sw]t0h*HG I+PLi=^,_Uh#A$D&c|S̜;Z9'l" aNJy^ƽ}O~o8v Zj l}X@Z6 WYB{9WnZ_,s07֞oq?q3gWw:srꫮy䑇O8zcǎ :׹TftdH/}@4$cu# ISyx27s rĉcAhMoٲeppѨ>tX~--{vmY{xRh2D5ȇB`:s`vMӌJA26PI)&,<}?/nc|s@ g4Tl=< `s\>%/rpy\,D$bYkPHIhYJ$q̀af?:ʟ{v2&IɡeK;A CT֬c]_bޗ֩'B⩧wݱ{$Rg,9o Vךآc@&#_I)@poXw-7;(DĀWHȀn%uF2 kAe7T1EL@|;7`b8mRϓj]z[r-mV6:v1!0.b 3Jhv:.)R2-9u۶:s*zGa.юedkX(Z%Cݽ-\\_wѧ :ZsW8M[JuT5M<'az,AEnIM{Gd[k80)TPnfB!wzϕd>qJBfNGs=vw:pdzԁ!"Tv@[nFFO>_TiIc|jD}xEqa"/tzKQܹr)$!,Z:df纖viz4CC+?gzzz>>6D"zRXkS D90"k@4@s=vd=Np8[)Tl{y/>Zߒ%gΜ{Z}k`2E`9fxظI6$߮_i%9>#*梅֋-Eރ&%]?1y/;#2;)^zwឳmb$JI!s@$ (}%=i7"OyAc|GTCSF1GQeeO~? L=@8cev (\}L%5$(d=J梺+=!kպԃx W]wӵWr82PSڔQmvXZs9v"y3qLٹJR,B)z*e}WWCHX ˕zC^(}D`]1SGH{RLڐ҃ \t/N b$Dѝ;Ϛ} ]@@Y)kNܶ}[Z7c:Eǎ3b^K7aKEbfdJGrȄh3c#G  `cT B tv,Yd銾ލkVA2Ȑ$CW[{^ CYȇ3O\b7v̓4~hwu۩"dY@mSBtJz(G-Y[Im]>ڢrB!Da9y-W\s@'z֫#|he+Q~q-xD`f" r8l?X@GJ 0D5ר"0C|RL4$l'lܲ\>ͷ8y",KˆŠef?P M{ a(.n38%i8% &ν>,_0.Ñy\{m=_{ a"^ RnP.={߮syuNd't% 1D"B(%XͨIHk6D~ 8MP f #g?\uL@a!,DA, /|cChQ]ٷt@A$a䰔\AёW۱奏Xҷex @k x= T HϧgGf6B"@{&_hM JzW&fgg8>51T֨4[b9˭l\o/=W\y-`, qQ,lEZ92"%0â, MTV=?g:unԈz,^uUȰnڨA/~qǎ33Ǐ $5h؆*HDBFr|ϏL`@Lli5yۯx7'!?=--'O/gGG_zё5Wt7nZuіrch kAJ tiL";fTB Ȝ8:fNTJ옝}`uFQGXņi mףkiMoAfS:޷˅}=> c&xrqHE0%藎%^{KVwTFG:8@y"[ȣDiYB @_`IQ7v<а]!wlXg_ʀV91"B_e6Wn驩Z nz2ͩj`b[Z/aGg:rkA РiVd]uDEs.hatbeJNEqC/47nؔ˵34?y*ir}Tg;$ <\[vR*S:Co:}lrNj B&FX7_}=&bEJS5{u87KR*pkGccS88BFV(uv[Z|[V^Wn-ĥPR@0'K-PH&Ahh8xCxnY\ty)%M kT% 2 rmhPW㣆' ϾҚ)73^NRmA.5Fɴq'*__[3l /t v/o5 ;-TROeDJBgQb t~3˗/vڵz$I:5;vze(N4 mplS}Aڔ-y*U-Ơ榴0%#'}V`1|]+-W_384\}Bw=;;@) :D@@!R$qJJ`Z tJ"svFDR!f%y{y| GZ^N{yK"^g,ch^x]l ,Tˌbw^Rd&(%HgX@$$`dDHHs- "H~N|gjI(B!$jqH|O=<Ǟ7]垂OԢyuZ[iu~j5#'( ,Xʴb~D0$fG"IA^Z8 yqō18{_}kKwX9q{K|#Â;\.58~lb%PB%Is$@y"쬳I<ERCajf})tQ{KRV){+w|b7߼bSç4^v `b(*BT~.Vg@P gI)r x~EcK |0鼼jf-(N H@im2**IҨ>p_|DN^;:a[[Wڲuىb{fcs.0WD.1$F}?\ <&UBst AxCKow]+㺮[s)v紳Υ@h$ 9MSF{5ZNr TRTݼݹ/|#*ߦT!/'XvŪ+\0ƈX#Xv^bW(0zFd"NR /<\gGf$ bJ ޥ _wn/ts#FOqn[s!9n+/+o ~Κ(1uÉ@vZvŠS3q=uWl/4ap$kq#|f[*\qb˯흜>}`eۑYZ[gs~n3\Mdn\d!l9s߳O\g/?탇2( .$X1X+"Ax?jrwr{h'h@}@;>DHH hlLMDcP,8 $O} d !0`׽q $\*Cry034̓gm˅ҟVjCM\V-0D,$m} V5 1\K4 5#wg_MIxK_v[<7;e7n%W B" l- ;D^DzwQ8o2 ϙa>s˼ TKpyε((l$ ů8+lwe'NkA'矋<99hCιZ A5'9yfdDz=(TGNiha,x>1kLhkXveoC :>_!CdD x wB 8.ΰM+jIej~|rȌ(jMv" D!ȯGU"'QCyB*1P(n]7R罊/)!XSWvLA޹)b3cB#n-Hi$2JGmmM;||3 :=(gڨVE˗/[w#'Z~7~q M4iAHpBy}1/f{ @,C "w,)^%|)5/F%,stu ֶttu}ؓJyX>+?On;=֪_W8={v2כkDid HI&BAZ@sҫj PźI?.AX0gW%]/}- r0jTBwv1S_&|K{jsGGRb`` y` T!0 p紉]RGh)8gŬ%(lb&1,b H ؈F$I\$I-KcR\>~ӧ|LT\l(I0̕JGPzD|\J6 s~!BgӤ+A H:uJ9p mۣ{Ԕ/q}k*r Id{3*BDq"qRfLg9_vCȑ $Z3C!1ёR9G?\j+ݺQCa!PZ6 8gbjl=Xǀ $glrdUo@G.q΢BsFb zjRH I"% FHAZVkp|M3W䕽o6t /$I3,|?6zh|fk|cz~耔;rІe-tCdND6VyW:R~e!~_wtu[TyIJQXeuMϞ_H &08vϙFl(La (K*la[߲n]K>t& p`I{)ڊRhcF71 x{99إq;:[ǫBd5v9:gFB7]e|)k퓯M:7x'$`KP(%1a3Ws6ݼe#vWSǥ 9gSm~Pij.nT{P(,, &! %@B5/cA6_,~ Yy{رoپj*g3O=땝wu'N4ec`3P,<g4"k,qPkTѹGL)٥_8yd^B⭷E鹩֮5+B!ZZʅ{E Ja1{7R%]Wȵ@kWnk?}fg֪zkX|_t_ݽGvJk `akADwW#Gͮ^fe쟟]jխ\q|ٲ0P}}] WmYđ+ 02!O$EDRYf"\HHg C\hY~W{)hʂ e"ZQ(7uT8Cm2mz$&tBoܟ}՝u@ f`l@@RgÖ-+֯|, R61e$I49d #gܷ2 IcG>9aЂr cm]F %9rV!#6'HCXK,Xp|H:3qF'ML)'@:k93hK!!3ZKmom#u+l- / O/?G)1*5ȃxm٩(Jd%JRkP Q%}k^Soic+,t|;٧;GNwj" 3 RYyCUkܴڶ}bxġց)F BVܢRr͚dg[/yrvrT,œP \,ŲŸhHtWmZwC7]sM8ҥ0LV$.3"6]e?G~s;BѤ-wukPWgkow]iA,apAݤP N(ϝ#ogn?|xQOz T.^-"{F  ! BvE+6jR׶ΏܻdGfSW|-i<򍇖Xsssr' RH9rlQ0Q 0瘄 kM.f^{Վ" MOO#5|o~ǎT[lh&IkKZZJR[ʥ. xM&jb{J56ۤ(RY<`g--oxD]?┉% >~4cKK} ۏi^i JI.VimzzRn݆q|䣕J+_j;oyӣ3gfX c5˖w<)Yπ܁Fɑg=ssoqY{QkM!暫P&I4<|2իx:ٞ+ܰj*kTg---y)#Y,FM)ebSE3eX:my%| 4#;fi )α{Dhv  3#R/K" 3GqD:_{+V,[rٺ 떭\~&UYj~λ"b#n4lJ ;$HUjÀ8m߰+ }m),ο3#kn6,g$;q^+K j>=:33 H@hҔa>JӅZ 7zmnMuO?_p9ձ5#DȓR$zfƊ96Zc [v}4Kl-9KN| ~?g=`&ЌF Q#i][O>cBO(v涰gmW}M:94ߝ4& r[Xcvs!9aH>]^=N>%ա*sc^߷wuYy lf5P0;O9~r^'>X[|)Z=c?_ߧ7>[n^CU.u׭ 艑; ý699y5o=tp޽+`v'i*Fa RkdJiF@*ct5gݥ] ӵjML O:ԈkڼthpvnZ,[l7Fqde$R.;YJaJJ!ɲUR8g,{kPr'֛>`{Mj<،uRxpk!+p΁O<χ}]{͛77_~<}{qökDlm!@sM̸m3eBI $5aP3`Bs(!ٙ %@M/x?k{;x[ IJEHS ߶~SK[:`nz"!8CHis@ F;P)%D"MRkt3r|*;pwoߖeKl Lf`~};2N(khV!rֶ{nZsh5N}{a :n:ҩd?#H!f'PZZ/~Pdn|~6^.aTtw BX@ssөhIS]+)_zgOqk6VPAƝSJTkB6&g>Uo玚OVf+֭ 0XZm~ĭsd/":r&R*Q `b` @ l8k׮m۶U{vm7l93$s<;߯wə|ߋp$rp =cL>(I!\1aM9Mlz86Nw>{ѱ3BvMWnZDKnRΝ8yS޺u-s"MF;bVJ C޹ٽX4;/r>Drk, I<#笥[~gϜ9kϜc/ĉ "fXleNAPnmURL!v$HIO:#Qj"x3j /ӤRHHREyz~R2b' T4:2g\f:@Eq0IZ::ןŸ>;MM;nYz@ iĎS6;DH{L~AC$ DFfdRdL态$ŌH($HʼnBʄ]/|wLN40_dD. $%%zL3]8ds=m@I١ F"阥jFVO NY<ĤdARL,= yezKB? ј,ݝROY(u"&c3Z@=3?1b+ Ţ#g-;Ў}>92n)#=ĮGy ]S$H [n\?ټC0%g&̀s*\ N\f-0)H9tK#?+ut :$3*Pzao溳GOp#ΌE .UւTT EHZyc4NJB]ܛ{__T,󹆎 3N//^Bֱ/kt@Ƀ[09z2pLYH|(gտ cL I5FH }JL!p7e$mnۺ ^vLgjxԓjͷn[ru?.ڼ/~wu_QEŰSB28KRe).!Z;/0Fg_@Z:x#z6J\^R 9s}U㥃}q]Q+;mp A w ^^x13ڕȌ9"RD_棏Zݵs'B/ E~ؓiij|ш(J z6?W+֡d):g!izu=e8nekG9Me%eDZRȠ9FĦů ~$ٱrK\~ؒTuj˥Wg٥IKz۞^T`ͧS3c,r NƵ )純iya&3 AFa22$|Mfb,䄗5A(%ʃ]ܱJCWܸ 8iD+6R/Ƕm6[v뵯O>߷ԩsE#IDd{ V<"XK@HdByꉹxr9kA#у;^9h*yeǺоÇ\u櫶\991>3=ym7`FƆ{ڻznTqn*!1!d{E3erpk. X/w SoVijCUֱvEuZ A  5R!-3 AިyYzKZg_`8S\&M)5: 5 16D`& dv)(\lj"$kT֚z Bѱǎ=_xR 7n'-ѝ={;=$TҤiP@ !@55ւBY&m7:+I <헔W cuH(!\-GNW-_z=wo(ms:Mׁ쳲 \ lљ0|΍Uf'tap߇#oEvxN1,Dh``_y{靍j,c.WJuB?҃٭w޴tMk_h z{_:>vT3! ng^ء?΃B/Gs]xp {qMc >PpּA Fj|N>H:flCBy]+zZ`EwčbOcj ÜG* B惂>,L5v&DV*))JPRfdRF~}O;fvVJSM`*l $`3Y"vjS "J˗OœsH%E .ݹ32Υ[>z/`oοˇ[#Htw7ꍇ==?mmJJeHy@*08"tΑPYTII1:f6ڧ쫄|q!$Kw}Kߚvu޶zR欐䁃$)c AN w[)h<1q9;5:똤‘'>/VzU‹gABtY Kz13=޶ӧ\WZ?3:M~P(|_?ru]v7xcϞ7púukwu6|_zo|ׯ_vٳg_{mnU'NF ;–JmFF 2$6TNkm |it:J|cytn\J /xE>6sf$JjZe{ώl,@gᵳ?T2"I&G2 (09/dLn+O?+ՆQ`P *ov[ߴ&4) 3M!bE6I$D`9fl! 9=5ۨ'QZҪuk83&>y"E|h @QN,g:筄V^uWoۭ>ɕ[QT|E9w&"RfMlʟO __p%z1QtŒܼr5W!8L`?ZxݙYd"\u|tK{~zfjvf fFŢ*0XdhҒvuwSHL!U$ A "JnAdC +OBԈF#:a9FI:FO8xIXk*' sWHku@ɇ$9o!5rW;YJSED}Ǣ oo۷/JPmK{@ 0s㲭Wu(6tvv[ZVn]w 4Cu[y `ѓ@Pű  )H1sPd0 ɩI&TeF@mN&n_~rz@1{w+-ki)wuKB!hm-"N(jm2S}DYty\N.}R4M$a AǞ%}Om UOc%bSRj``ظ5vhh;$^x{{{|={_޻rsOď|weoq]wY_7{ݶrÆۏ|#y牽o|'P~ۭJOLr--w511wdy:#ֱ˄v Рvs۷/CA`~=>JZ=3< 8&8C~B,Ԍ/h{O "fD+L|M)fI wﳟV67*B=C}]=}}\mk7w\8R3:l:b+/&Xp=i$hXRC`vmK#ãDꭧO v.Y]M¼ԩM$W->)2e ;\ܒZ}|2>:53QAKxfAR ֻo:RHuy956yv`piRi\WmW]YN9p-6 QqQk9|뮾l 6lR&i Ղ72]Ɵ;B-p>.Vc\}KMFs`ff$v8<:Zcg8Zuڥ[VIs7}6)EØ4pֵn. U^mJ\ݺ%I|߯be͛~ePKKK+U??ޒ/ R{kkwWMӶI_iSK[c~gon+6s_6 uMLLU* SX9j ab-*Q8L-!z* y_nk+Ӥp4i}׭}}\|P6u{OyE %%@f5eӈ1!;dN#|`Xkm&H `<8/OoJۼRww!tt\nM{Ogh2l~ހ`9Ea l%,0sف&r 1 '&T)Zah`ՒցeSxa-JV)$Y6$J'@O`ɗ_?=WuFLqLRw]--ꛜib!m5T\Wtvx>)ZggQR*'NL??{^^ [%(Uko;~9[OmDA"]Rb_?NeR$I Ax&u3\F9=i9a\*^vö+VGzZRTZ;V8^Ar—(k%5vfY#ęǏ;*W}Bj[RFi 2U=;K3!y.VsU=z_li+V3gOٰiCwWϽ_/f;:@RiB?#;BfH5T# $51 DX`n\0,aYRo^|<Eww}0 Ae* K{#ZZ?˗m[вz~̙zjÆ m-/=cfzX(>3g;::FFF=;v"AFB>?;7 cLtڰU*ZEĠTba :pЈqi C4tP'X^kspdLOoMo:}hWW[7"yJz B9{DqCL38g)0:ZDB] Ԣ}n;lf|˹C =]+^RQ+9[%/t<'#''3B6_%PC$J8vͿԙ}]}֍[lm43K$Z8&iD]3 zR cl5k )3R?՘g\b٫b6))/QB8UۯVBs|S蓹0$P&?jPG{-[^x|3ijɉÇ |eHjciA&G(2Oɀ).5`1i;yjii^(_˗m}˗J4MDD6 &~ZgۤXgmZUcDfD*T~K>SʼcH!\ВR>\uŲ [֭r]kgۺSc$2 ŋƧ4t5uBٌĐrˆOzi㟵)y1p=߽ו/ǝ3|"ʧN]}>ofr֦& 4mX*!5Pն{>r׆+׆yF81 W,]7l99?__ћ BgT_~чo$.TTm4Bu+mqQqb:1a`<FpΑJD>;s))\`(¾mEol[%&:I`sHFHD@>AmnH]8A8f̞_w՘NM mX8J;oen/[6N8g>{]w?p2Wyۏ:zdk׭sZuphh5ڨ۸!Ϗ*W֒Q2' AIef,j;Do{JV/z~q{E  \ 5e}NfГږ[ZBCˎ=kǎFU"Yj8;6GQ#JcLjvJg@2D?Pҁ+!1TՃG۱p Mkq}Y|{㖩M7ؿ+mXSl/8;7;ͅNFo)'ssJ%s:BxX!8Jd9.#Fg \rz29>BfG|?;9 DNKy?y)>Z\ߺڞ˯_-U*af%B$qҰA&"}1g+BJCڱLF] E!N+'i "Y3פWѵ\jCW`Lq5ZG .,Ԍek_4" +zZ-K8vgWj65?u&&'&&$" hRf 6svZ0\2U~3nTf'?K}ScSns% #" LԹiFÓ$"!-1 !|>ᅘ<"(JcOzᑱ/(M<9ELJ-׬+T>4HR$iåGid\ hX b dGO{d~+g!Nsd:gxQ>֖;CngggG`l)_ e |XJbsQBȄH<88qU__WgfacRj)yǽ~uxv:I>mEˠ!}mX#׸*RVCᒶk֮uTwwIbvu8^[GkFCyqئ$Ɋsigglik<sF"t?77~~hO_5zw]Up'OD|ǭϿ©ӹrqK/]:0tgG6]ꫯq&Q|l7DItj -[9`؁$%CpXLR4w"x* Ws2-o^瀡iX\l  uyխW;mAjN:~fkoG' O%{^1;M@D>Qʩ2wutH̑c駞{CIIiWgв}/%#gNMM'm5R\Re^{>#vƲf6+;e߽ 3)8N -]sϜ81&P9̳<77 >/[zZ:;U)q*թ c[IS)R*j$*30M5!YDDLuH *"@LFBFD&ˤgPHTc&a Z$Q+J@27|?t޴0|P@gwm ==rd($eiFl= M@(Gdu>?]ޘ(ovxWoJ88g2c<'^]P*)QJz u6")tfcPߨ0ZnYnmD{Ъ!-H:TkI~3QH5$fk!9٘O6f}GFDi%&!\Kܹw{>+E Uk8lޒKSO>+ ~iApЊ5w6CGL9[3Ϋ|wuVjpL$I=$,ddZŴ\ ihmtl#Kudמ+[Lq9K>K)=? PXk8r %ޞ|Rd'lټM>kVJ!9nFs+Wz@:}bo UWvMTm{NO!G"$&BiH kOx(}]˗ sꪫ.ϫVm"" (S޻*fgj$نd&zY~7$rV*èQ-~Ů",:BJ4 ӊȰ.M]: $gҋ~K{/~_ꨥ[󌌼HmKJ.8ֻ,PXf+ٓc&6RH|r۶_+7fO=8|gʭ-Q5ydb#xL(NCc:?."+ph9c-]Ta?p=X7E%Wtw.~/zfwOp(e^.u.id~~ղXʩ^Rp|+x~y# 3Ң4=-Q1йvlb)y^iU>ϿU+/iR¹كo컡w(Ȭ u]םZ{nE,`M")^-7ɖb;I<):ϙ̼Iϛ$8'UUjIVoNJA^z.qHYr|gU~%dž0 -LpHnOk KVY  fϝ;?=164bŋ<#Gⱆ:pmKդڍ7 ϙ;g鲥-CrKkk42"%,HH ,P5ii N&񻕠O4>DbBTF0i>=ֶ&}0>5F0!nX/7,mWR& kv4r{lv_>|XiPJDŽIq|/VP3Vd`|lΑ4XpLi%~1zq!&2g;@j2@-v\1wK/]j+q!CQh08I|@]~!IA9lv-$\kw֫5v.4#Of" TKG;^````ȑQd8ZSfc)B7J!Uu];>b UP2pc%Tjf'!"cQW@>ۢ9؃Ҍ.'xU׶5ݷ?91[řXc &Ф?c;*Hď+eYr董c#i7h2Z+f qKX]NyX6p;/Ͽ9q@sG\/v\;dUJ6h BB"&V&>; ƀusZ~"iц_y1;ڞH$:8`NPOرchhp֬Y:fy8GF;>s_3wKׁk c,ၪ,f!H @Vu~l2yƅɦ=c9bMzC?'4;ڜ,kj* ȶ5ʲ | %2wشr劮|suk<>䓿xRűΎ(6&uP xs߄;jo?bq$`G`OaDT╝Gyng"?VT@aGB!w]3$"M?tPJPT'[`k7 U<ǥ50<O&-e3͐AAΉ3zcM훣/^ʝMW#@*IDj0dhƴ2h"*KncjQ[ʲ`g/h M|=?=jloxlX)5Є`DQ)7|vp%px|iycc]mŀ@@J\y+?(='rR@P)/EŠW ~]x>!cC !csLCd(5Ad"$iDjU~K`NJ~aŽ[SF)&5z7Ωj8KβS{{J%yMd~څ[o*uD:rm7PA:h&Jc*sC`R946fG<c#C8{Z(Wb*20X6չre%%M_X.8 dJڄRQc:"0FGZ"h`koY{IS&ۖjS(6C.PڎqcLV)\̀໐+N0U1 2J II]vmraset@Jq~sdçc55E'*kVlNշ{6?ɏ^0{"7l Y˪\Y$bn:"AX`bZFHY>` W\;?ҜH{O^vDnY헶\)fx#w0 ]H맳AӑF%#CrO+ FA%˖77𸕏-'%pȀs"hLE ccBsY^NBdW#R̶J6FT*UKX P ˦hof~h~TVwmswSD7*nAdd(A7z؋s2_OĬ-퍖%UMQ"'og*ETwQsvAw~OY޵,$Pjl>$- :z'e 7G#D"8z(Q %fpIDTżLbop) D:{ęKi7 }ChI;:"Dlj! >;]8'xdԊ?ۿ\>#21= ,/ D}gYfCd1f @LV P#IQ++VXn 0 OFc -Lܪ> xL*$*Uw,%fIO$Ƈ{wߣ;{ub0>6n Iؒ%5q劺D46Tp 9ckI2NJGH8vcw ?UTQ4c튕JbTж2x2Xa]I>q#]!?8i]37a7[- Llh=Մg{?%^`R6nl=g~C\{s/xK;.֑7cd U&QDZM':Zε j[ٶl!a4r?yL8zÖek*WGݾё˲zN:S;}_%[7X"ҙtLx.‚G"Q@R~'G,/AVdf(!!c 8q$PU "k&\ASaM;w=LP֯T Ak[}ߛnjÏ<\ˋ,ֿgQ:C榖gNМRTXs#*V/( 霠 ۑ}z oŦ9}h.1$CY&ӔB%;;s;PylَR,VJ1TYz:lmkLSoRI}F5󛌓H3KMa(2 J0Gh2#1a@r/k? UEʠQ,Ot+.\9֧fJFJ*C(ƼX1/ea ΙzR<[!c)0xCkbYoVW[Q8nlp$gӶx#JE\ekW/^ޔ`tp̲Ìcrɲt&LA } G9/宭^ǺFsQ@ hQF!V- @{xYIS- ~Y $M/Ҟ޾Hʉ|Tw1WJd2a+/?8Cg^۹;g_u[ܽ|ùmWlV55u1 hQgLQqC`@ mDC! A ߴ@hJ~sܦCMkjfl)˕}w r5uߺ[uSsvG 8v&7\(SWqPhqҊ a:3]8sbDD1SL űVqD; p!z8ن{c;gM6i%,Mʲd31pۭV.yߕՃ6nIߺ~Kg*#|/b]o~%7>-,d-XeP^ -e\@doU3VY0  h3gFSMz6zg[4K?ڷw8AضE*{f bnv`l8&t]زM M+"_*cB>OX0Xta邳ͽIj> SYd (k~k6g L9Ov˪ekxLq`G9LM_{/R=7 M@3Bù~;6oZ_D2lZkBf +3"m@Q ݥ\Z?2NX yˮl8^H2ҡ #D)% p#Ƥ:1U駷i:Ri%kі#"1A4J~^$o_Ș"e;bt -^X,hG<~ T| $II8>1Z,]J8CnJ׎~ۏ|~+ )A pzNCjk??}b[zzR5I?@M) 5XT`pm”$KoFU~')vOԜۮ˴@bQ]T.wBPʄI`ͅ0C=-TܵhδEL:ej7;.F3FX.񡾾\xX5&":C"Ɛ,c'$".0+ؐ_ ƀ1Wׂd`<(0mێ" /w3e {h~+?sO=z{kۼ> -?S^ t3r2%&b uNni- i x:ID6uG? j l:&:{^۳wΝJ)d~x<o}c/8y>m\{ESs[[ b\Ne-sf hq DU4G11D0IY :xOoU{Ko[vox2^ޅmiIj q"r`kr(x}t5[۾8vg>G`w>8>2 < LD Ȯ}{M[V54%e+0D3?]@W?^0Yc |w]@k 1V%.2܈XL3OwA0Ό6L&#J۶d G ސ3aM"n1/N0@ X 1tPar ޶hd飫.YpBOg m\7Y 0WX c}CÕ((t=3ܐM\puM4@0$dq!b&=K_  H$ϱ{Ξqd:1(b1В֌"2V?Q wwnlhp='H([AKLؖ_`쮉 7-7X!, f[rUK{[ڨR3%=x3lm@nfCpr`~ {eѸ|]cc׮>[xxn_ȏ^3HRFb*3t~gY sC߮;JD$U)CcLX͍/]ͽ;v1LO0Мj}͍Tӌt6OY H#i5ٙĩܖ1.ppṪaT?vhTBDnҟ{*ݏh8-ؼy40@06HR ,HD}6AT;TDڲjAgPTW9~PჇzkle-vgζbcu/Xélnȏ?^Sj[8gJLZضT:ґp,Z`׺9]7y-W<;6a򎃯9'S_J}GTRoe ,ӈ_n,JCKsTk복5T⌃<"[f=0;?$]?ʧ&@&Z`9. _<癧Ͽ:?˿˟dz(xN;߸/JTvQllW|`o/{JkSUf qiC$q$"y^w> :1 n^*ñ^,r]h\141n>!cd)Yj .LibX )ܶmcY0e[k]< Cw|X#N;z3w0/ZP"БA6:ϗb?x O &Г ]BP pRfI'_x;PDgB~]c%ק볆c2&U4Bf۶RU0`y9*%L*$񸧴BIJ*g>vzpт[R.lyΪm(2 rJ`}}eYdd%i|yPw'.YqɱNLb 9q2GĉC-/ 'ӉP\hod2}o8?0o8,Z8l;dBCTWNq߫͹@g8%H3)(;J1?3ؽOǚNS1nK_ |~47/_X2_'_ٱlqG){n^!rDשJ1TZ cB@U"sxˍo᭷~P_˕l^96ݱKj/nrneo?d[G=;ql@ 'k|:7w߸Gᛅ@x/Y iVf Mm0ǿsÏ|oyQםltc+!p] \@%ӟ>?-XR-Ẋ>|e c"`T}F˿-M%Y9jҩ@D &&.3R^N*?y1n  fq$ # ȁfL0AF ,"*^_d޶bKy,nyB*)`` `gKڄKh,o.][F92aG h"Ȁ}7S#únjsFĭ(PGc AD5 g:<HimwT%8okkmkkm(IUOdH~x*۶gvk[G+kYh[W% u/-BF|b MlPڦD]v<^.J}&"Y5-$"c_?q"De<C0gt 5n{޷xْrP*g wŃ(JH`@1Ĥ(N#ΒsRF (OIR-mQAgWT}Dm͙K C" `ȸ  !p @ȧ19S܇]cxzb̟;zKS7^6o޼j?4X²8SOjXv(MT`Ul& )opnYHJ  $$ETd(D8kϭk^۶p]w3wT"߳/H|遒}v%+W?rE T:|(S^1 4 CJJctqKh[Ҵmց&,5eK;rt˯bX_ ߸4a**WJ1#>sӍ7 6e2BDE%P56o訐x\VDӆ?s M7bgNp3p,a& RddP@ -e`If,e~KSGcm]Âm5ٺ/޽\̖ͅF#X ѱ G p笹 AL cLL!2gOʺyc8(mC7=W`J\p!8/ ߋyW|CE~  B6{鉗<X#Fa.lc~@:~h=0s\"l$(Y5I.!A=q c! 8. qhjkۖo."=kUCTlc6Ё΃>v6՞9up]}{{bllm,7&8t>7a`{d7!_.'lǵ@j$QYBlj),aV[G@莶˯664&jkL¦6ld{9}dק> /ѿd"۷~eKv. k׭]%mmJZ) őIv WlشY;/﹫~vSGXo^! ~}O>s3 .?/.oyvn<:1%O {W6zf˲74߻K~) g_<|l2&Pr/3&b]㢍Bt.{oNB~ *~fEi `8±P)Se EF)bHl.Colo;{HpS{}L^رw 8؆׻7֦iM-̶)`PWN 2ıC=吴-Gke@t}6Yۛ>Y eXP'm& *oߋ"t)CX=D0!bX*m1FsOI7nY{íۤU M1L+0 ؞/bq2wt`pKm"+!0ҜFj2#p;rXYSxт{Ae|/\X9hX(} C 0І@dQ܍l[ЄA1:302thQn"-kSq*[5͂0} :)"RО=R;h lel{c-ɲ%XF</m 8I!ɒûX]<'8ž];R#2yc8̐ L}}!c&#*|ȲM rOш0 =nj+ V1JKTBj ²kۜ[Fiƙ,tv- P]y3V:3}x[gF3qʅRw"\I<};w`n"s39?];gb^ Zpb1 |ѮFprإ2@(@e/'8Vl^ G_^|T*T*ۿst)Cy봃ٳjigd?ɅI>9g]dR)k##{vܴiCN[+Rr5䙡/mo>u3+.5\ ߷>|?_oR\剃74$at!X N0+8~PIU4EDAX„2gm;t=, %X,aD&Zj]Ks8!,KUݽg gz衇*QnRxM/[d=#͗ٽ/OKRZwm/BE`DjkX 0dg  D04i dch[`heVN͝sS }YY,񙧃rqάt]n]'ύm\wWe?zW̙tWcn*peۓ8R  @X=y@&Xo.>?L͟L-RB/1Um1UQ;"PոS9" @ 2P+WpAANv}^ħW79c`s]p C2?8{|˕AY yw{=~lXk9mIן֬[u0-@CtщF] YV"܏<)M gd['p±ۮ;,,̕RXkkK21ǔep:WL4 !-6& !ʸPum_e"us3IXb愦wsZ{8z0rʻ!1d|Dd^Tn媺t2ʅ#o<_뤢($2#gE]8Jo\AG`eyHש^MI ChM (;!7:n R r y=[,cH!ls7 K'Jdx~PiH[Q[Lŀb|J;2`~T /jJ.崄qߣ<5|NiHm] w~+n=}{ .[1tMƯfŗo/vn#w^ᇿE_ -M(y&"9,;Dki!g wo z{nN2rӭ764Ii(Qʆ0-Fjgf 2 C`Pqs\!LAQ4Jd hULln?N`1C*1׋H%m4r~ӄc@Jڶ}ڵ/YXm]")9Z+6mۍMTb8N^O">oRlmm1!-[j ƍ1ƀTR*{ UFhњhspz֤Hf545L J+&g 9tOvY՞H 0@i?Dcٌ$R&Ǽ7\+* E,FCۚhGx϶gw_m RQ>1X9v *"i;`Dcq &8BXF9s(#Ntav&65DV&$2\80m6ük6Tp>U]՗zUۿٽ+\* ]s{w9}֬_]GI҅sZ{i[N׎-~oYe naF+7&dPb6 GڏX)pxJ>UɁ.\-XܩPƖ,2-Km `(Īƹ !dHcVʍJKDfw]B_>&ܖR@ZsB09hj m-MYF [6gq˵9JU,"2Tcc֭[ya_Tٲ6 &=M 0LM6ڃ~h:)ʧOZDJ42Te!Y@8  `-if%[g4 i`p+uύKs|[Z)'Ioҕ.]#zk%ыWxɩ>]w`U.Ps5 M`!@*nI3(&y Ȥ:۷-cW}+vgu`={;:N;.^te~c膆[n'ҩ _l ƲtG+(IQ L·yW4ˊokohQu%8D`"qDu B'>:=zͷ=xu7]?kU,A+Y&H+p '|-^9͚zP(BnWl1kNg|xKc׿@!B!,"p<*=&O`f4D"}{O>T̓(+z8#? bXV &7 c j*:voWj3k׭9uRy˖ֶapMwkLf#c#l15EzjB,chW#K @IP8C }AbVqN+5M;ޡ{+;6TU,`c#y>KY~z5'wmሔ@X`mRh2&XEFG3u6˕ hCڻǗBƸq⎭FFNJBCcy͙RpW)DRbOƲ@o^⧷mxu˗/䒕MMߐ%"bq;Jq7E+))n -[IlU{obWdbhGؿozǟ{9fW2U PKD.Ԟ9-,K ЄmoLgk `LvE$Hd*;"2D3%u("m\.'SYWӵ$Ηv HH((`n[?+ -?.APr8=q'N؎0tq>77N?^TBUDJ#;#AÔB1b1 VC?jPNT&d(h8#Cd2Mfrk@` &DBEe {*4rev__Bt9 ,Fs2V>ӕe{OyB~g`x䅧o u>tq>[\)ڳs˖MPA/' E!:1AOZ`CF!UM lٰp")[XHAvbW͛bʠ?۟{Ap1wN&W~Сlm VYu왾z%CÑ:;;Lq+ӝ3{K2^3 ~令O"fՂ " !p;wxO=E/|kL~7`G@drEK#ů4B155ް0($LJ$ԑ"Uq 2VXzϽ~E{Kv"AWH1lq%s'#3JGI_|11;cX\g #Cb!B8c" C2 DR ,-&P"L&j,*Š<\9Dɞxq8/J%(s-JF# b$MO:M͵p"5dR  !ql2FFb# q2/xm4pp'aYmmmaKq339}o^SjU*yU)1o;! 8 gRba4I$ `0iT, 1dA6߽#wؙSgR:=vĩS׏\}|U6mȐΜg^@̵D|ּD{-/Y8.( a(CF+ReRcnеM͉DIACǂmfϮdwo^ >Ww:rzÅRa[n:up`pya>?841we@)pĔ1PR Kc: Zi.8!MD,S8j ԥ߮->}XW5V 'yw}zW_my[6ݺy9+h8 <{-7zc| ZnH7n (4k j63?(g25Ed*dh*F*iۿhϥnoV*!=)::4ڐA|dxoXha@ /z5Z{dfk:Ҟ & eQ56O>“/+lYbi"ЀR%5:~q(1-7<:ᛄ&ٳ۵` 0 gL+. =ϚfT|&>XdL!$qa61fƫ/p R;4+8٭W6![S蛗w9qj!ّ)IqKm5u [t^S{s*,aGbZ,QFJ+$`R?T'CWPq"J"bi:+ޕ+BjjjS[!'#MkM\nOtuu'ֶ&&rɌSM> BQF$K~vfE8CPo r&nyDI9Uv qgDDVdzAT\q+kk2T+/^mMG'ʬNB-8 HA .8Az~b6U_O|e׎2ₕ2 XA<NjQ ,aΤGzs#Y;2gtx "B0%Uv鲦l*e#,_Д@+~ƪ-ϣ4Ԑ\p~IbUt $J#IEH8 ؼeU˖$@`g;sԩ]1k]'O"W t\w`xp \o476kG~7:|twmm%XPkPўD?l媿ܻbh_Ё(:e~O | ^5E}u7״҄vy%KVp3EJe.ݙ`cG_?;?u ס -Jpb"*0@~tg^bYCKkdm1[djL chړ:mZf˄uqitQ*x&?'3gMfoog^ʗ 0+!ڊxEQ{ C\<0`t<]_24r3wrTFm[RQL$+1Fpnn]jN( C˶̞@omTƙVu-Ĉ. 9qI6+ 1ǩOk/Y1laYdQH!"PZF12)u!G4oٶEd6U%"J2Z w[JRF0#E5- #]ޜ%Mut*=oM ؿ?8>,}keã e4Ih *yU {tqg`t0cgV9CC",aAȑR)t1_~gǻ_?sb6w).цBa!4R^X1T5ޠ}^v0 aZ1FnHµlܻϞ]Z'Nb}RL i4C+ -;o5+W/>y|_^Dܱc :;q'K""sJ*uV|Ǟ׹o`xdtܐB@N) ?uq/DeK_'N_d%CjIiF 5660 5Ãw}e/fYE\)"ڳ1ʏ@9]ɏ\;l]ژ #T2\܁e8Eͷ;B~MaTR}=Ct%T0MK444p%)URZHJӭ: ޠ5>0E\t/wו7Cعf`>!@)VB"EDQ LIon}K}A)`w?}`g9{hoL&=ߵoϾ~qApM̓#-N,>=wPHAf l'i˲ A$\.=ޜ@(km{ H@)83ƘU P)}W,8Xq܅z-z]{]&~W:;;-]6j@ 6Yax3͜V#B::Ǚa8:[y?WH넗 OL,\C?{lk{ g졇kngJ@JVR#}}gNt_yٶ* YWRYu( C4lr\\E k#PRKe2daUy G4g TSM"/Ow5|g;c. oJ;ϋ+k2Eۊw}d PZgFJ Ms-<#ᅥ %,KqYRB߱$Ox*121b&qIJ!\| #R2J%8{~w 547u44564\yrTvv̵ (\mL*Ǔ]h'? ٔuh!W%#cTkَp>Ha!@мm[kߑF! d$hgvsKŻ ]׳ I~ ۊ]GJ=IСW8 " `d1r!W2Ae`-k,K{SJr.qI1IU6R|7OȰj332fiG oˁ硈MGd ۚ̈$A!#uu bHk!B2 \@ C@n @@HG`ŭ|3p@xP\mRT.)=e:Nx~{u6̚չwO>f7`dh>z6_~Բ1&@tLX DJَleX1vsC޲s7OyHש!?xbq޼S\>En‹o߾}GvvR>䓵;[(bX,ςkQQPSqĪЈU5$C0jpR69Nx.FLJ%2 ewYNGb9߻[wyM7];2җ&,INlM$VULUfy5$$K3<4smݿ̉A)󞛅X壒%" 91*E0Cdjƌ⏲~.t0]A1\OJ3R !RD۬9Mm(9-u)ሐt.0 9X]ݯ><3Βɴpm΀g*hK7{326jwܸ$)0Dec I0Dah[SGas<\,?u;I%I&\b22kkG?E] pfT;g  T*-D S19{[)߾CodIF@!cLm(P M]]]MxL ޾>75^++Z߯H)}7DrcƘ*H)%8PJ Bp΅JJ#R0~x!lOR,-1Erxh!Z66q 7ōxAlfʷXw7y@JACRYqOH(DPADf98:fBkZj+6,uGm׏Әm*Ja0`DsnIkz lix0V)qTqm#*{`׎}CgG}}3 X`$:͵-m(EeqT"ZB$ں91.!jbh)C !2TMk֬93Di₤Ȑ@+u a9}Z+5IP_[*ebW\9/w4kR%Ȑ1)BplU.  s4B6U5MA/QmVϝDoqx JEJJ%xihL,NFýT$`I$Z"Df;ou;MYsjjP\z۷&f;rd>08:jŚ~sCCC{- &r8?yX]i@q 1QEbj_Qh6eYݸ Yfœ֎lX30<8N\9sLXWP(Qx@r)I_a鲥 3ښOgϝ Ɓ ^''=G@ !@ؤV$6 Hwq*u_ɍ5v/Z+/AF` `@e;??cY ֬[M_Xx7dozk,T\S@N0eĬ7B&ͼ7N7MT$Jdkw}?wǮ={z

n\LpөDJi&gTs3.8_`W\R {yW_ݑwxJ0XpAC}L::|-m}}x, 0?:VZ…n eNR|g9`Fo`օ1~J̔4>14R*(Lc R`p5],,/Bu -MMӧ{֮s}s޳Dt=/=J^oᦛoc'ګM[PH% p*3Z(1[y=mYWicjkk[=\oټ% H֚&s~iq> K;zWV_uP-'!T D2 @ ďDRM:b@D G:rWWmrᒕApǧ?-uut@plȀ ~%PXl;n5Ҳ\ m[RAWt&mۖ"E )[l*?>~vsZcsUU߻o0jnAW 62qfyl 8Sˆԩ.jI<.9AvkcnB+mbT(5KW4jlzEmͅB^,\dwLLLJFWw>rbPȁ#=_CK͐ҕ0&=UȜx=_wU} :t=P#f gma$m!@BX(L%u'>g86>ZH,26lmaXB8k R_zWd='Q*m.cuݱ1֖d2Iq1~6J9Ą?AՕ Y LX" Q i\Vd(\ۍy VdmM|WD=}njnl'#,aʪ;621kǼ?6:˷n=u'֥K]/KCC u.l9QM-ckq[p۱q=i!Q%lR?йb3PBoHM꤬ӥrIH+=:Lyjm9bζV۲08"R*&*b7~Tj+ 6lůZjᄑm흝.}g,eN#'`@Ȁp9UvyJQۏ ę'p1@44W& B32DDc,Am[aY^"rxC[r`xq`h<6~ :0ȓO|s?/Xd]Mu .Iq)$ `DDž0EcFzyaҵ ʌrs_ ! M,X̂PJ^wT}sgX¼}yX9*2"p]v)WQE"lm~zW4R^e<[12"xz/qϙn1~~p)UFq.vtM:@($1cq.1cH.G'؞p~X9oۥ3N,9{zO|uǚի7g.\$bq:ϬP.V;;;K̲Q%ҁ ĕp_oשӑ4BO  mPBr8Vj0R b/'"Θ1ѓb C bvҶtV$<c+" uGO:ulxnf56dr971ǀ06uY2#%(N3D|KKmaJ%DZRٲcgeJ ( 4Z;hKgB繞*}1D $z* ?2D"SlZ`w쨪83 !;a$$ ͐giз7k82?z"W6Ҡ9,a+%-L;e9pC[|  کFwe[p:/G?yO>{ؾRcFî_ԋژ 74v+׬&B2dTB!-[$Ҡ 6h)8cB%c\p3HŝtkS-'~A7Nr~txdeç#nჷܽwu+':BcFsM.]LmvȅVW* b}G>]-_/9yfݯj˿>1{<- ,h8w(@\? 񖭳gww5u45Yfϟ? Z̀!` F6 f$.Y+֦믿?ӿ???X{dʘS]wf݂:#y&}7n4H!DyYY̜F+VvhF hAڻjSU]ޤ|\7`H)9AgUe|y/>?w^s.+$E-Y{? :q9$@8YE@( ^l)uZt]*K6}ar|J+ $7v}iMaw[zǖQ2(v._*x;Cmg|7Wl3niqzoxza7`J2P)}m ?yV|s_Ï|}>p+R:*BY@dS)_kcޘ5$K$#>h#w?l=0&/O?^nFŶt&j6[T* |!aS L:a,Ddu^, 0AHM0µp+\ݰqzEαuv5$XGRj*&H/CBNȈ2YXZ:Ddړ ?sıa#l0m&#"{zrlX8|pO}jbG! b=[ɤ[JܨRX3~ow<ZHmt/(L&u8r}m2e-pA+ } _H|:8L.뺙LFk"QRr!'?w\b`].$[~wa\uf?aHE E'O__b6m6˥v'vҽoyKZGݾc+!Q$$r.'H/0 L׾T p{ ,g`6N..j<;wv>(qoEwʍA)l?&SzzN|;o;h[dC< LʑWN"}K^'GT"w0H\ !4rk//" ڒVʜ8۶nw?/3sg/۷޶w~W~K+&3\+bHq~:5רZ c!S:#*hxı^g}/{-=ml˥:S\P;ClcZbHStMVSc[v`/9qݦ Z #i@07[3SH2~`px14oQ٬)-gll)D!#!$֐`c0- ] gz|p;fOMfʣ =vta`{N@SN^oE򳹜l:B1כt&#"b{{8z]-t]5M)lU+uc G'%F*4&bܺ 2bȁ7kq!x&fL7`p&JHH) ZQ9ւx=ߑt6kC=z)HDkcH2!nիKϴm^z…Kg D` ٜ>tpȌYUQ {ζb1&2Srcs.'F(*Bcsǧ&'gϷZb-A rw9x4B=LZ0#CHJ gǎO^<CÇk s{Q\:wbsV3@XMhY% A1`M jU?{>֨NZt7V/?7ݴטs=[7m^XXLS6n<•֪> [;JL5wf,\ #-5?AJiBXd5d[H!:hoȫvCC ^zyi[SalTed7Jebbbhpk]wיް୷`.c:wXbI4 6 Iu(oTGlЁNG>Rm]߿;x{!3rL:9?9ԣOT&8X ]{xׇ?x۽@uMW@ dC_9sQ6zvˡ?dق&f[kU d8YdrWIA |#C/YK֒ ch`P95Z3ɹ>7Q7V@/@Gyyz϶&c]}F.NFwj6Ck$!"D|coכQG 6{=Ʌ h@KްvR&0Hө0 ds`•3ȅmfu7tu>08}7} ck(1"" =JZ% JG6+F,  K_fz1d-Tjb[Q2)qq^/盩ThHBk +)EfSahoy}Ņّs_DXk5@4?3-lZa߮|cnfxՆds6s L6A ""2foXp{% WLcg.]ӹ8rZXh0s wk~~kG\=XZV0j+R~Iҷ RYG+HeUk˖-۶mD)k@G< }=%猅aT7.O\6~T xHްJ1ܑ Jd x_uZzOQ÷ܷoϕ+WN<ַ5c"q i4CKsN&(C"lEkK{UZV2p]7Wv+0|J)9 C5^-.O#XsX25!dM9cDa:eQ1Ƅq e}ۻB/_K5'ǹ뾃۶LtwёGhoxh/}6/}\wWzNa00eۖbW;pV,p@.'mf:=B!9w&[vo-{o! v3]Q&\lB{í\j;~Wȁ`XV;:\ij5Ϗ\8sfan=3/\|߻߃J9ۺz<"8uv~=O:%G/yn__G?80jut/gП oݿO#w1tu y@\z7RFk׽z5RuMV!u|^trI/xhɩaʺҕȈ#!iN!N@Z1YZ33agXڵvlZPX$%hr$@БC8eZk,YCtD\1z˵&޸a`R;w+ό^s#}oGu+M`F[@jsz%֭f.\ Z—JiȒEBư;/ B< Lh&5b 1'|GoۊϮKc8rXZBFF'7r^x~nvX,Rt# ,@Lk."Jkt+~(jAБɟ)-.,JG޳svvV-w]o4?6zС7oЗ|>s[R\ZIe3H4#mȨ'IDpì1@&V)`R.xuB3@@4R394y:1 _v;HKYjtr\Y!(X W>*@~? 80œ'XV腱0 y/R2gΜ駫o{QZMLLٽCi:ڳmMQJ H` -X4 =`,l Hc;Z ˼\Ր<-đ^LNfs~jjl¥'7x]ۻc[PSb|Ht׍aȁMwB\Ml%0# ?[׿ws33Wtd$wO?rK!#:ʶDyqע%׳jG~/;ŹZG7 V]7ޘjq.[ʒeB*W|Jq\@gGr9D MNNa.ۥʮٔD4::zqc 8!y+0@ s)C,gmJq1|ϑ@n{HA)yNϕ/{N|g^[/_kZ.\ܙ <3qat+W>L!C73O=m|833yy{;O?v[֥Ҍ9})ܐ!΅q8d @ kA"t$Km,`vP[opMbKhzL/rLϻh@]\Ipp&޼" 08fs m|oШR~9RNS npכOw[lib[q߁C_{h`x薻nPC %@[0r0FK& ɵYNOL~3=umnݾy:"4I!,,%*!ahLJqh!g\paY| Uk"$cUTk:X;f58OA ,g7Tɇ%_y ͛뮑2@ӹ fP#.{T*_$\Xc%Xcl.ҝu Lnp 1% VpULDFQmr1|˗JhA)&HMFƭ1_2)wz})3Ӈ8|wW7H apѷ"`iXD T4TeEic9ylvWgϦǟ8X X6TT[]xˏ*}o0LZˣzM{`L3LWWqd +ύr(e~橽{okr=1qOo[o Q{֞ڳds+@py<8BKd;"vz?/o6(*wX3IX M<_+?y\uwPn޸yˉS'GΝ{G}ZlVmٲ9.ڊ8 O1!*¥ɩR~ kFH*D1VNl<R:@D(#7'_ȳ6rk0䎷)ϏGq=llYcYX'|GR:F'_^[k\)RƱ6n8#]߼Ci]*ۊm GFbسgG2ٌgϜ!l6[*W.^ceX#9zO+0Dcg.qL&U]7ٳG^8mϘ蹾￿gjzq~~n]8 xPblsdߚE}\KD{zJq5F^txwuί7sj~$.gU ]Ycy!\6P84#鍻C~3V՞zٙɼB9Ӷ>?T g~9ܱ÷3ŏ{)#?Ξə o:ppjT&hP m֪s&\0*Xz'?OwvmڲipӐ34ĺǙTbjI-Mm{x'ggfڶm3'Ƨ1([M5=9ӟu=@IoJ9m||tx ul 6ص;d,Rmeb]ŞĖԺ"r ,P޿[! Rrd-uf.y$322\*;kP!_pQZjm|6a3#sҧg=}a`!䢫6opO>8=>uԑ7lC gd "ODRK5`oMBHD( Pzq]]_=|D)Rzţ?v;2ou8 c\!E}}\sŝvGJa,hD ͑󗇇 }]lWW|.]Ԍd-7&8%c"DZ%RFz\䋞oncOi",%TdC0.@=rz~n|vYBs#gN@[+h4 <а{?TLJ_yH5 21"d$q XD&^ekl|ŀwvv9KDإK)(ؽ{#_z"p*nڸ`cȐ!imfRHRVQǾ5[M!x^Re1Z)G mZ@3桾wӞLnع1VTϝXT*r+䱓' Y"x(" Ucj?jURy3 J3}ܣ}W&3[rGG/?;wx1N.qo.a@ʢKiq>8+:/i I 4n7]@cp " ZzDX+щM,LҬT&npR`%h|}?"eJFGg˥X#O<\TzG*_b3o{r_ڑ{⦅DAx.u??2N_>uĦ;;?>{6 ͛7m۾5B`H: ERsL-fCiŹزuK{{;ѷ;nk4[1leʕ+;OOMMy sϝ43æ>hoF7 m}..mc\B/PIFHprYu+24,@d_g-ن c@: A@ "(޾f7ضo()dg.^giv]QSSn2=93~ԅ"#gxRYRzܪu3O<\>.vw4PrRh^ArY[wP)nǮ p.b\p$l^n}Z%XKQ2z=yǑ+Wkn;?O= xtm"ʕwobgTOxo[:sSsЖ9b0ͥ}sVYn3<,Fwu3Pd ]ݹ., /xgw.gK-z0;}s@niV'Ks- D3nD{&G ksf! ) foo}h)$KHNo t}/W&S33ӳ%tkՉًۺYWg[mŞ_7ZZZQQ"kN= g;m׮M۶m޺u-[tYx[ssG Ft&R*D0K3_Ȁi(t@$Cڦ:#~3%Ķ~bUBbKs\ҸC~&[@г~@ A*_|ؗ={>*[ȧZ^?oY]72]ۣ˶D=ZZK/ K98%EQcǎ-?v+ a2g"$j'_8q;82,) fXc2L&6V`d(+ttηJV"pҙ\OY- S6nj+%֍r@o1LfW^h@6_Z dS9|TBj:BOsL+w69=;w%`wbmDM )DKZ10`< EJs32]m}o|&w[o;?:O z-+TF8q`/2˥\ցYQ< FQtE aVs.δK?E͙yj^OwL*GYZ7+־<]Sc!DPGBPpgv4DsečbRыnwc~~…#v8q{'L(!8gaB )oerxo{U|!Zhxҳ,M"! DTJՌ0Nj | ӵ|<!mԳ_ݽ{Tynz~flO,6+,$5 R6 eT\"r\oYd$ZMٰrfqq dQ61`W/+asܰQ|-+7u/;~<Bc?@EghAHv<Hy1ΥZg׮bV&_$' Bu[[b:R x1SS6 6[&f(*}xӑ^Xi!!0nU\5"e׿8Yo̼tǞOg;# 4 o7'k2 D<,s7<;4Իcr@m071ԼF^losR^& C乙Xŕp[WoZG֑}?őiW̫gB>*!%gJ=DҞTtH;{GAYg;~ر?S'ܗ&]R*c@Z% 0Zdw|&[֪}\yV֭RcRC, \0CmnW/KipgfO>S$l82\)m}b CQB:SjKt`a\zBҚs kRƒd@nVqHhʦ]eSVr9߳{l add/cqpĉ~:777web" ŁFY0eg>tI>|dC872? jz!,21c~;mǁٷ[n=G0lp-#Ƥ..' ~ˋ \۫~u:+x!.)տBKI7[u6 Ge[c# F-6`j DU?wݗ/S#Co=ۮ€T|V1d67Ms=o$2{%3F}۶?#8{x{~~}tlLHlBtL%&=b\VWwYz-sGFLS-m2͸"DQYKS 1LJFp '3jOf2|>OdʂP@!%i褳`Wj\l3g GKF;Cd-C9Al9Rual+`NFjow/oh7p9KWM=Njl0J7a_~?nA܆ȅ7T:ښx6C/W6?.![{&b2 0wg)ZWGoek-gA/}[o*SZ Ry 2yH3|+glIْp|'x52ƀ5JwK >;#ņl=ߥnhz.7鼪sH$MHYXU1*޷3b~)\xA+xROwOVk__\,YTڛ2ɬ {zĘ=x`*>k'2]w8,HHH6 h7N*eU!RqX`L1`8{J_fZ+@襾k2kV [F Q)^ht@42 Y P!(r %h rݲ':{ؕ?'\%@j `3>drQQZ3-F=GӲ^/ _P(x7Fq{ Co-תJ[KSO>;0yL%, Q6 R@d2äbB$y? q(""+e 'AbBzjp΄(Rm[QpsLlY0..%^r#0D4޾n9waxǽ\ؚ).rNkeI؛Xd\iEBP:+Kl&-Q1i5I/޳ekOgg'\|"1 cm@UJ%u9fD&,Fa|ŧyv=z+iI k~Cۆ^rAĸ%DDZKo֫?؏|g\b<U qt \tE:S)Luu v9$rEځ|kOߺ_=Wס\}ryJW'&!Tpk&sξNl^ #/\xˡ[Ξ=/gfgw]7n6==oaBVvv~l&޹s{ bTi0֪TkbLZ/~ǟaD@1-O` xfgh,y1_ 3ƔVkpc l+h)IT:Nj5@Wn$+M%X%=LҪ?UQ\)Ėrv[hގjTھiîm1pjf\"ؕəHlܼ!ZA0 !VcM>G\yLЕ\JJg\oT*G0lȁ/^X6n[tf3]jqёi4c cGy:굙l69tsʮ9CU=2Yw~2 qxP6{mTמ:vM cHO=#}=??9?76o8bnn&\k$)VW ]gTl.H/ ?_7dz:mu!F<_֚a,ח|?n_ʟ=qXmVZ/-6ߴcd÷rOްo-7mژNgYڱs򸉬ֈH;> ɴ pݞ2&7= (yqv3mq#yը!cȚ\Bı9I|Q?|c3o߶8d+J,Q%i9݊օtJEq*A 7 I|s[LFl  23=1/1/]:ph_GW{Y|'mZDLz4U3g3a@k>3:qƕN/WssSdӾ391=yW6tBGgqr-@\R;nx~25[VHX-8ZK[Ge;ocFњJbWv1 ;RsNpط\ɂQv#v6cHπE 幅{ᢡs4?/|rԓg żfzfn>xp]gz7|9Iۮbl7jҹje2C Jd߈(i3D)$ 8 Fın-Jg2 WՐ7m3 x:r@'a )Z> rOWؖD 9C$ %<")*M\z'1LJM`,1Bm!SnZXXk4)*vTHDCp A -qoQ#2N H\]xrF@KUl n!\b*/И%Q:lf8lK"#@baDT0)iy;w*h!\5r~xrTXXョCׂ+?d`LSkmR%0 BHa/M$’EhdDZ5Zc<i{-;/aӵXZ<}fs6aܓ<-WNm(Z,<'f=ʣ' k=uا5zuE+h~S]]|n[lm{ [7Z-wtu8 $0JG 1B8h Ҝ "l:1.U]>a1{~˾y VKpϏM-.,0d H! ڥ_B%iG֐X,V+c|s)gs ݞ<ЗB  hYw@ja.gh܉C B1]^&ǧ(f#W: 3Bͧ؂NLl8|}^xh~o{[ϝ=Ao_GP-DF1&9bLk؃Xkb?`i^佅"A'!^nI2,k \SQ%K#B,+|[6RKOrnԉӋJ+ b3977U,yFƑDf۶휱zN ! DV)qf=$KJ)8)J+eƹRkW[4= Zゼ@r,ɗA"VE/ t`oGwǭD৲Łv? rezXhk4ZT.-#m3X8RѨ/r47W@.V rSF}O.>YFn \:1u}OI7ߵZٕMj "dX(Qea [;|2+._YX<r :{?4V},, `MoLUBߨfA[ Ą16NUo6$ܑ{sΆa֚33r?b4f+vհWSS5x;wʴ?31={6 6j%0N5j (F[kl>RDMѨPL2faNN]RɉKӀvtl*??䉁{'ҙ"g2^94,{; )#<$!o.T1dJFhKkX;/,_pA@"B!OlH5^k19ƵoR0"  ~wvBրVqGPf/ݸc];+O|3JG9>q@0__.jAy`]T:;.w]IZkVtu^$No,kC/پ䡉g m) ~W/nhV -W,i (CHD+Wz8ZG$/N} `ϒ$¿Z{2tQ u_-.K Ɛs`qHH nDk 䁲"l1^ o W\Zq@j@H \#2 ^5^ 8^xlf 9\)( +}"j5a?ܼut:krƿHDL~ĮRv_UafQOdȮmZy߾t)P"d%_dpX\H"r= #L:"e0h(eFa %t,)dIJ7 fKZϸqtlR[\SoO?ޮVjI C0e+W6}ӟ}٬k4)߽i }}O=D@C/^hkmm|>Wȣh&\&kάk5ޏYN멋K}.0tp]qDM,"Wu#븉61oƒc^i9eR*;sV.;4 U}0 }6ǟxppsǎ7VDQ$rb F[+08U RVDVy}o>Τj6?zqǷmVBOwҏ{H@w3F,^+g+O?vq`޳ aZ!%V 1k,K{'xKdmzU8 -M/u21^Pjuڏ]X \Ll>\nJư\dsٙJߛ-.^FFfJeBTd5X8}W}% {.=t6#O###}}G477ȥV_k 8cZ)4D!0:Z6Z -Ujͯ=~ gsϜ|S|ff?3?:ͥmT慄Zǰ,D~6K dY֚!5eȀjFqljz*']OZB+9nnOe]ۊWELDE-VjL8ZZ 1*^=k$]ϯ *^( \cC erj5V+R]vZ eTO{syS=c֣FQHG';%;dyЊL6V}WE+ɭL̬iDu+fmiowSGf:Bѷ:VVs!,Kşץ/YCX9 !8c\7+kkrr62dH cŠ4-˲ى?/ҫ+U}J~9\K(9l)\5uuZ Lkȓl\H.^<9^8Bw?%PoGDZ+eJl5cSlG{GR!!%fBrGtTU DdQRe-?џ֕+\8l&QJhm\p$$"$F#CW( u}(eXm/Y"O+fa< LMMaiT.Y"u=sl&s6 JVl6yF+DQ8BJn:;^1ƊVRJ)Jk箛'NĆ!Gh"Y\S-53,%dZWW! 1 !*nY/uY KD6  YuýRHq68q@΁sEǮhr`bbknnnff:"D L#c2:nZjK~ˤf$\=5txB[>-xⱾ تVj+n8 HA`l)l_3d4F+yl)[;*T-Ђ]Y2^y'q rL+u2`K|[r-%NӪ#t7zȳK?񗮗(P@ %vo}?`-0oT7gSHɩoܑ::J sjơ3fNh AA0Vsч~gw Uf?qD\9}zaqs&5;ryabjEr]wm|`ʗ aVJeMMmmfinrr`˖MLR]$Ȃ%t5 Eq+/1N֖kmsRF*7yh +FADlq>3;u=G:Ahc5 .8g, eU.3:]'kEp'j Hr)C}ѯ= Q<_𞽛R(ߝL &u-[:::E cAY溞RlCե(!"VK2(Bjް˟!)8!.k=eR+20B2KumFu NFEP!rrJ6 bPřqFsNdu,ȉ_ܗKAw\L܁7DqA cPp7r_.^8sӡmKJzx$$,G9RJ9>#* fń9r)47X)T6VU.%daUd 2QJ[kiԊB2Xc81kJaVeۆ{(13D5 b`7DKy.baqqzzq s6:%ldN@xtl Z#Arՠ=U};~FPi+pU@3Ƙ`LpX遼0jZAG.OLhkaiKh `E)%W0;Htr4h!-B m Ԋ;Q&wXKt%IYGN~> e >88UΎ}{;;5EaPLgQX.(9$ Z\++d W1%]KPK8DDvpBHAMC+͸m-""<D!9DiGH!(NuƝ{L,b-/_++f`UGәbZւ 8ҡ9EYP.L#_{&}}OK)F.^VK?/D6W V@>QB\:!WO;HJr"kfa#oՔ;QqS )>0;Wr{7oߺc/s~чWm9" V~> o`Í;6l{{Tk֮ˍ{KϜ>}y8*V3BU~ȹ顇ou<5a/apݖjkKǎ|yq6(hePmS`Ltŝf8c*Vmžl>۪6TBFmm{ 26"GחdbPgWHLʹ!&m6B^H{?nTG1裸p'|vӻo]G;Q6J sF֜Ⲍ#!y#l|[OF )RƘ%oY,y2xp{!@2%z)kf9p"*H[eiiNF^LÝ5ZKVnJk%DdQrYBdIJ1RbM`JL6+%eVЪTLmT-͗ BYփqXjy @|>oIt6[Ah6 A[$H$VWGDǒm6F:stc+Rq+*Z!:z\(HE6 o^2 7z:r$H\. c@!4C՛<[d^(%t6+ZΝ-YcV^9o4h:1D1+ ]!Z+41dޡnҭLkl|#N'W/mZiђX"My.H${76V+@8܉y,LXkKqKZ;>v% L67qe`}s\cKru]OYk] 2+z:gmyDqJ$xԺo;~=DF!BPhko lbwPXTz{9|cbƆg؁ٙ<ޮ Ea41mgrzyt'7(TMUĘ8CGZ#4D[D!8_k~pmZBMD@ `h 6i)%rG)3FQ;#Oɤ0 Z= ؖ|l .NGQDZƐ0V(ә X×gYrV@]T^wY}Vĕֈ^Qrkc15U{s<m4tOgpAx(VL63ۍgΞTʅBި͗R"X0Bj  +-yԥd,ȳg5eԝwV)E󙧏?z+ƍCq NMOAkmwP@Q\Кd@ Eo>qA7t8vvuwcޞ>)`3Gr$%h^<1AQBGS?2e_ggtuvۊwᩉmwqǝ/y)jTZnsRH_lݺ{DU"k-A/,^'0<$$N̕6T6+)e:%3QZA4<8rdc/"X@V,T*cppcGF|?=T{uQ1Rב8~9ljD*!]_#׋h"2B_?m|+#`O(89{r)C@/|^ ܇VtM7Q#Vʞz^ 9oo `oƒ_owrzjwh??ڸazf~玞9v oO~ֿ"1-~3rʹsg-YߑFcj6ZHN_zea/VDdwPO-Be6:˽:EeC0<:!*\|\0( 7ld(Z46"twhcFTpM6I\>53ӻÅ4#ǜn-:oPfU]Hu:5@u]d@`c]?ŹDc--{"[Ka]xqtt٨#zz]wuN>}O<zdrӏ< ȒZbI@X2YIroD4^;Bfp+tmqulm ^}xQK\3hq"aqnUW7@@v ַ}Id8ua.}Y{z[)_cK1 RsAtӍJ Z8n2iBL (v@!^R Kb|v37b!W}V Bh),h9&B8~>FL:8}ayzz<ӛdž(>17?g۸&xJ2_ G)ۯ:?/c4Mjc7 =?7ЕߵobQ[Ÿi#jxHq.F  شX[:8:=1_*CϿqSwyw~??O~=6u4|7t#?NM--f]%g. 53w 7,k_v˼wϞ={tQcMo4#8n yhY#Tf/$@ko< Yk@hw+om޸ ]2D@F)!5yKrmff]*y*Xm%ݕ4N7mڔ[䨻=ޱܨMNN9#eo[“YS]X?!+}ƛ]EXU{KDnDJ.`3z9q&7ꗍ^TFo@H)Y! Wʀ/d2Jb|AC`@@]0_ ߀ӻ7F1$@uhTԱ>bd?|G1 +%IYW.媵zww'ILZ\g|Qzͽ}ǎغuˮ=/NO??ul{?cgONm/5.N drd$ \A0Gõ_W*_tw3vJ; 4Ml2V'en[g%`c Z8?qny~O_yu;l.4Ik5_oKœZс<l8Mc""ild~Fac@h31~)Y!~I_M[ pF RZ&g0^%4Cʂ'(x6M#\x&MSlkzi|tS)O⦡P+-\*t'Q$@J,%J `C`<IMu2̊Aku3M@Bc ;T ;JʮxT68vR&q# ֔ ap{H/=jkN"Q.wھUrҁvyb+i3;,L R0O>|QxũRl_}2jdiÚ=- (1!ǕWzvmĽsHnOKة-xHZCC}|[nF sN j%FZ&cRCΟ8ؾt'|ϗR~_-ʿ-O9| Cπ@`ﺉ5vFЧ?^[O\Fy?T:?~LBtldqǷ;~:tϟ?LlC/*vy242jH)4A@Z1?kq!E)qu{};W\I+wt@u)`o|RPFm`=OhheH+sA7<;?=}s<% R ըVW+|G^8ϕJչ =b7[F>\-6aˌqRAeCo;HK?rd l WPe̱v ՄA߀k7?3f*&b]BZkR3?? 6sSR4mw~WZ K RU:N5+@Aݰ6a pbk@D+ d U 9)%EI(Rɜ﷪+/^\Y^a&DŽICA̋KK |>=[FL>9wvzE.%DY5) < # Ą2Uh# ב i? >Ӻsِ%\/sDV b=RHJ%fV9灜(R^mڻ{Ѩ Э r<4֮*pp{}߬3Qݳo'C/|;w'xraW>v=4z ,җ`XV{jz/}K_ܽ/ޞ[J>${n\LRخi8yp:?;?>_ s?W>!hXnN8S?ySR+RVϟrMD,@EdiTWd},Tн ӈxG@q#4-8`A(bW@@X'|^bn"J"=vر{O>5ZGΜjX+eC9Μ8pcn9~fiηFʛg..ԪmًZ+iE`9}$ @(CMşy~C$Gaڻo=~taa_'>vEZB)_(h!cVZ,JIZ tz6 BWXu  =H _cXiaX/:FbRS1bٱhz1 (#si! +O&&$PZw ]lN97S(v;֕bq|tT<<` .rb9ML킜#B8K5@fSLB|d1&;r# O+"2QJ\B󼾁s++J)_8L̥:rlڳov~Ӿ AUcu,\C|Rȭ?+^q6U@ЧϑЫ5H6TwsWv:Z.~[y/-_DI-"ֶOf=b;~خ}{/FF.WTo<[ 0 M< -,rGՖGv%le/k8VPJ۱W@jC [>{۽{ͣOdc\ ˍJRy> l4.Z)|;i/<~?ϗrO?Poɝcmf39]*BQӔMYGJIG޷hVX}7po?s$Qےa|}a_ uǯ߼曪8u5:VwψN"+?%ޜ@&P> v6d֒"1()}wMe:b`B( Ξ{P=苔:'g7Wc4VJP򼼀oi:kLk}Ŋο=O2C${w8 |(=#p $67~^<ۆlRv-CCiҔRY&lHR@eߍO,4N igXNLB['XnAbys\SB؈9_Fo35zFDb`gjD!cQ i"$lVU#%/g(.];k"gJ@oPJyOz#,1[bT%%^ o~]w95&1B4[kf3RH"#J$i(\bH+;O,4ݕ<1[K;︭V.U[w(*'O ՕمX^hq_&2JIH_W\ls p,./l߶ݿ?>я~S'zSb_)vWd1,(6ԦљS'%28;;5e\*>i:?hW/973yN1$Z*믿l%6:5;@̍ehhs9ї(i6Z $Z rjJKWʀ†zz P'Yxj5{f?w3 hD`b*(Wm %/ȃf,~PiTk6-Rgm Х&NAйgo=.oa~ih>, ],4a" @WпeR'|=ͿPLCʩ|(s:\zӎlx4y“Q79⏧9@}YL8Cܲu( azgOL=9svXZmE1)L0u >twm[V 8N$E`A]ξG+@*iTⅩ*z n"f4 (Z>:#?ӍZC@@.KKKI6CTRڪ;-gtGj7&txU[B;s൩_H #G0&2ɜx#8_ $!D`R0;{?qi&xad P&}GԪ=z˥|!nt}sgvd= sgF)ZqLD &rrD< bZ1T|ٿ2hRWDZ!PsMZBwMLTj+![Ͷ^=z c[-./O\26cgvgAlb`"*[I֛m \5jP[kȓQ |W.,Jcͤ9޻wV@-l] H\.+mϓΥijցR+U? j({*jsϘ$ hxs~8Z_?¡Ο\ ]nN cQ !KF=~L5 "jԁ}lF__N" XK!WkFg8<ށ^nd@ͧ乓j/P*(X F @׊ڒT,狥 tL&Ih$J ^WX/шţG:|HGexT+DRsk?Ja>u8q'>AW` " fNȦ$ɂ# $֛K׍VoV;5Mt]̴8\e"-$H@|/בjCjYʌ`NuěA\O_k ]9ʮfK kT5W|p-@)mi+tܔB°x``R>d`OX#29"ZK0) |(MRK^F!,yhmsᗵpf(SL1Ʋ[Qjv܉vݨ7yc}7?є~֮Kƥv%g׮uŊz:e'zH!絣v1ħ>}k՚[^QZ߿~~~?>?O?ֺ!v)$z.󋍕|!L6r%TRB[Ҙ90 sIob$+@=sՊ@H[!~Dm#ŋ `Rb+e &˵ej)RGK1l>|(A^ AdflP*2vm]4P At )lǖRR!`+NGGRZXUʅݻBOxpȉP Ξ89y|_w3˵{X, (d+[x^!B塯@f hR;PLj3y|#p):ĺ5еUͥ;)l.8oؐdf`d ^ooiBTÂD $( Dp5 BJpv~Yy5J1)I  <r9ֶmX t*}!iv"Ф$@FԦJk 9vyjurίg|R)#awۘU`!$Rn e ` d:)S^9ٛByp$'/)Rf\38FLf 432`4iC01L <ߓr6+ݥ͛pxOQXBIpDBT.M[̲  Y@Gt슻BAZX֪[?x`D; B}GE b\pN :(rDP 1H""CZiBA];'s3K3g'&+=[o-KvG~q" &)X#5'ե'OMN9&''FwW…Vj @2Qe꫈k YB ZͶR ;hAH!V!v^ {C4/5Hg…%}XJLqefdGֱJ"q̜iͣ=զI 9Lo_m[;8?裏ݸ}[yW ^2F#Ê|sJqrjml(0`6+]evveiao홃/M]N4%48m٨G?B6ÛR.tUJ8YL0u\6lf|@Q:K,o- \KxtX̜Y۬WrG#Ө$\K.sA \C=#=7߱~c?ך` bwٴZ6~rKS}> (H F&bZW.Q׉&&,XVV rvz{FLv*wԈ|4Ztzf{9pR@L./-az2$vye#l<@VC+nvm{/,/M:4qSfD; o'[Bhњ(1kuDp' Kڼ2K㹗 w~pW0U4k~/^YM _&`Ku@tl7k A揉ʝqZNPcjBn@3I\-=7o޵s4QspI5&MRfB\:+\\N~tەɱ`vpŌӺUaʼnS»8= w$wu~R9ugYXh ) BA^&)54Y*{:]8ëM]!_w1,lWu^vͥMíbx},(O LEDNjP_:ŽەƗrֹ\[\`^T(X |"d5DL;&chؿe# 烒o9 f)z]l&;w?xjA x~#Kr~@߈uSJ9dljJ˞7oB ƉWx}omkȤ' d^f:2G]g 4)(7Fm,z݄>BB)(Z8kJ{gM N I?{}{wBk[ &GDN  GkT$~R}@@pXeū.yk6A$s:Qk\Sh zѰbK^ h nk^7t6[:vVw䅡"D!b~o͚i4zFO(s&G \ !E$I$&b0QIZyibSH=㶝BJSDtృRظ@VX2QXl5U}emd %""b>iԋ^rfI^ I#hbua9[ aZۼgG-^8sP;GQL aKZ{fnAH[b5Q‘6d Qsʂ_!0gXc6B@̼>Qd.-_!Wb ,*4d)<_/doxMUumr3R,e==1N0[e^] ganA#jf"toweg224Gݬ56_΅0;1=?ICIRK`qD@K@gk"bRHue |钳5e\cݫl_m9fP@`SO?/|ęV3=/IdT,B۔WhVVV*.5nx(40kZnB::e,/VN H\)h²b!:&jp|[ax|>#@!AמJguHT*! 0̃s@7R^n`,RD\![v& Q1Cfݒ)|O|c3g?J#$IoXJοOч;nbד s ԑvufdxT@@%Eqz/RGHk8>sooV1Ưjg&1̅鉂{>?ONN={J+H]bS,§^:V:~Wsϝ/sF8H* `Zo]S)+PH)0ÀkM%$ |+vkFժuݨo|ʼn ;m! مj=M Q d$9hfUϝ;_Q/C;E퇌 )0HP#{г]]}~Oɥݛ6 -_^[SkΞ?{z]+S/H=7]_[] BtT`V-7n2vƯ&Om?$~u4L rT劆FDa@IL $0;Ũs~P*C}]Ŝ[t c6zӍ\PͥI\JRM(#ge>3 USp'Q+NUQ3SI%;U._O^]nͬϧI<<6RQ0?+ebZvֺ5g\NEQ$L,`n+xiExsjKISBH4lO?VٺISI)`XP2+x38uj~a 9 P|%(&oc0[Xq̈B)JZ ݕс񑑝;vE?m\ۡQT?K_Ec\ѴSgu(3 F9k<{C@|+|; 'TJ2L&CTILƥvҕ/ p-_/OrƅC-& RI%QLFvc{ =;S􃰐 @J@!y nD _C*~oV@`)eSO ݛRiVJ33<ο}=Dt;^^]7,dsG/?s_|܍{# UHƥm3$:FJHy3GӉKKT.՛u/\]Am:=@4T(FͥmcjxfP)|Σ*oҧ_]uAZF@I .YWiQkt_f`o)s3JTgkFjZfްi r\8#]R~lcvU,JgPZ"823\dѱRWˌꁍ?@ cr.HvnioޅɩbXz{+##b~T04|"AgY#$;$I.ɑmR%$bk]:$IkVd_p8vR;w'zv±C.;]aJ/B Y8MS&|2}B hZ?~oF` …{X1se,ZBJ?bD/XXXL!-@nvn^FiQ.Wf#Jz"I!xfJ"]E·;np=3K)2#DA X*9!eWEӕ󻄇~R\\Z\~||+9bJZ%خ9j sBfr+=RFZB[16ccQ;~ 0IRJ]S^i|][6++ AhΝ?cxzP(4U/93?F_H NxE @8 c _h3W˾¡ʶp!#JT*ȅ]ߊJ}^ o?~^I=,`!%4?Ӥ@k;"R)rYOZm7)jngWTwuWX$fKV`0Qݞ=@k-O{dXj5(j$*06U"VK'.L5P:McĹ0=xln"V`oUgc`I8ATvjfW7m6[Hu~bq }ߊ ǃ̭&f}Z|vٜnEbffH(e"P"(s#!{_,Z4<Ī׸=t,.)8(d ヹ. 'alѡg"D5YKAj&^m#e"c"3CF@H%δ~@쵦l @P3RZg\oo7ݴk J)_~ԥ!P /mZs0ss [?oֿnHHP/sfU) NPuuN#_P*fqnsVJ0JJfBkPA]s)XaW:zTl5ہ"@oOD67%xRh88sR#d^g&u l?رW/4F$0ў rlnyi$熆WVۭX /-wm#ʼn夆K=&MQJsyu` ,/P*XJQ `@ RVhsFe%(%3}|jH 95 ,.,n>~Mmݾ}o>SSM%ȑ i;s'5+᧟YYOot|] &@gCxr%X;vNOOaMZ~^IW}Gy{Xcc_YnGJ BκFNRh$-B>ۍxuV`f;pTUJ9%E[h |ж!UP_< O-5O?nESq6T9 v=J)p;Ėm<&NrGnlN/e|{CwoR˥1j E@pdAN랊iPm&&(ZkKS1c)O슉 Tr^7^@Bc1aHRĭ;Cds?157\بx;JZj/3I|y<$IIHR;DZk(ŋO=R@DZ }ll󨒸R]*wv!}\:GDkaVW@îeVoØ4MS!BinF%Y"2NW,km@^\ՂzjR ;QZ^Z5ET*F@6W]Yឩ$TJxO:yL~)XB(`Q_v;wMǎEӈ@{{{v9 ғW>5Ԩ ZuM^EZaI1e:1ŲsPP9o S޺sgKgůGFfR XR:|aW)=Dm%wQZ09ҁ&JbA֛ٹ$T&m5[iBA{9%Q7ٙY'g޽a&|Z0DQ 7eӅi./wg<#z ,"8$ZdN^43 P‰'lAxs7aSƁ omyY7ENMVހ4`p`ޚHMֺ"LlB򅗵ϔsu2 @# yqq<( |9r@lh]/K7ScɴDс53w 4O9AZmuiqgyKRd Y-^~rV>jJk''/,t1/j^ǟDZտ2R//H,٥8k֖[Kر@ÃRWڨBT1#az~.nZ)_i3g 2T>=uq|HW&Z ^0"?}j?W 7HHBX6IDLL) HCDlT,G=i`F6:w/|Ӈ^|i`ޝ{K_ڼ0ajeqV=_>ԟ|}]7W>0_.n9rRH Ɨ#(pٯf.2 婮^Խ}21eN y{}(@DDr 4&QJDf:8D)Ț p@ Y _}#?xDz۶P].OdY@$!@(PhM(, 2~5M$29%ؑTR ڦᱲ#`SR~3]}Ghk'gΥְiEHH"n~r~r_ʲtGCw (v P02v/ kn O|uWkܞRҫMk'Q)bv@'2]&\8=*Jv`A&]G °>~g/|#ܱGu3gwww uuf.S2sSBa.Dp7Q NZ&?oZk%eX,7 Cdȼ֝_ Z#_z .omYn\FO1(yͿ&{4[cA޹XT*Eh qή#x)6q䇀qxJWF7M:k8՞wݾ>Ә+U 5^_. !`[ Jm CX$mK!HvJs|l{orvznv΁Ѡ@"Is~85qs{n_ܩ3"v ijRXgَk߄I;wyPU=}b&n\H:"b6Q  `c#)Rf&ccH-^u{$C^?_/ )ȑ P$sv]V$B9~MԖ$vwKijQ6MR&I%&4W(xaжiJX`lR (x}Ǧ9 mTX8gLw[_\>ygf XغW ǡ" *&x |L `$@ V+(9Uafz! kHFA\蜐/ EZ^X\r[c2C)SkA.gLL>;)ٲyb.عmKwWY[e9%UL, łPɓVۈj|t乥'!ZÇ@ky߽w޳ĉCssxem)#aݶAqD)k1uEMހ(_uR'}߷BX,4Zfl#~1ߨI] n; ;DDE  CV`&B fԙFbGbm?Uq=5b(tw/Ï49UCGB? ْcV1HF!\οW k44yL& h՞6|>^NrE W#GAD`E SdBJk)NU*\γ&IigJw%B䶎mYk%Q=mx(n tjkonEij5†xM=ȟwXzrk>;9X!E&Ri4afk(_| lmْhxxW Y9_Q+1Cy=չ (&PX ̝C=ݽwǟ«ڭ$тDSG3jI!JI)Y+9 !T.\VM{Y8*JgR$ڷ9jZP{ #[{KϮqO8 S3=#{fs38 %F:fթAO)!тzdS<[dz|RoWWUJ/S]w-Ks O<}Y-U)WUzG=: TР%fWgV痦M?;sf mczMO? Y6RT' ȅp!9RRZ2`-X`PfZˀDv&̺%K+~(x 7T|`ǬFq9 !YN-/~]or>`(َ=?F1O<;ߞ8ow ]v_u2I[gΜ۷0Z1-DAwrE^OTK))IVLeqoҦC_ֵΚBxMJAMf -u&7 $l5{?7;9"@R&ת= 6%BIABT2_($DXYpIyѝ6pNolJXxf6 [ jlWJ`$EW! 1FK r9\tþ҅Ru:Q (Q[J/<}`aiA?awh{{9vByL֥;x@+ fcOjEEi 4LD+I!o7ߺ_YPpyR?N0wrfj\^0|g{遾_r1)V>;B@drB/8xdl]]3A7#Ԫ|!/j ~f P䈁=B>ae@%dJTmqhM0>m6ZS Sȇ-%$LH2H&`+$@9_[`] A.J7.5(Ԛ̇ЃѓPp,-5(dZ@$1xM(olbL L\UH#wd07t¹{16-7v4Mi4l_8{;rvs7_, tց33g8,Wu b("U.WJ0 !IH Rh)@i N H8j't,D @ *t AoihxS_w` B$C!z*Rll}X)yjf=st iepD  r H 8Zs)}\_OVHzBHv$@>`ɯ~oY 玜sCF'gI J!&os27r}{=ߘ܉S'HPju(@E{n~JzpﻯpO>+G&( J 0CEp@ $4_&^qvj3ars2d],Zƶ斧~3߯ʥR;S8taro7wӞۮ۷wd""cJAF()Te{"J/d%RJ+EFG716;\Pf'[Uz{gߙV]N+ABFnVVV(S#B2BDFYJ!Y=ijֹ (>3>O9zKm[`x}rv$64.׏y`]LȰ4,V˜8?9HBmY~.g>}}haqitl|ǎK++<|5+C/s]C$isEyXs% s K0kVy ņ(? J NR(\Xzg9wr-82] h٨mvW Y2,eP< ~?x^/=w&%!=_-$^|w圈uJkgמ?'7|k_@!kYi_>'[Oz RHD1 9VjA8bB[N *)-C}w]6jDJvݞb+ϳְ#!X !TdC_y1} K{JC}aD+C_$( 9)gNIm; )xû6@!Ē#Čx#1@1Z$xʝ9 2FP̍EVdј/cMF ,Wq]WZ0( Ibҩ s2$mwD+ih\{an6cWsK'&a)RAI< Ð#6~푼2@$BQQ P~C~6HoȠFdPJ^0"u%f L\@J߻usn(nA+0Qgr`] "{(⍁T* )X83sȑ5#\1u!0!@Ƶ[s3SAܻ\o{?+r.N5M'뿰Ү/LVVW z:.UWgRWѰ b^lJo>s߶__.Q5I/{Șx){{C ˡuwWqUl)2"C3i)$L>O7m;yy_gfȭ6(jYAQ?sx{PaOWs3,@ F)ףD(/P~윱]_=|PV| AVJd;5+ _wuwNjd_}#1@Z`ٶoIP#`bun@ zp[@j/6)YoL]mgs?Q;PȊ #wǶ{nzbt@vtڡ p4MO)#@`SV.M6gW>sބzyͥށx*" :ceA<:#+Ks_[tn'NϞ9c8 ;ѯ 6Kg`FGcgŦ1̳+6 CP AbaR+5 WаXYWo3@4uϽtxff||?ߵ"G>U)~H. eM.Yf H; c&M[4y"(ؤio3 B|SO|Kq-Z"0g&!! X}쮌XO# \bFp.\8==-A~SO}}J,`Zve(~H 2jFJKNoF*Ϟ:14fҥp6/<ۭ©#\t`V3dZI,d%F3q6LJ|hb {nUʗrf[+ZoXeq%.W+ 0X3(X %@*֡'O?[n_@X"_rNZ80x^,7A I$m] @@4ƶIҶϿBJHT$kSPf7(22}lz{JS$) @JP(θxn8(BqtplSy;v0no{i+9 D(L֖Vg0݅JBAB 45&=E7 )ǯ3=3m侇>^8|/86F&gr 5L\_><*_+dc(λxsNNc|~ss=Ž֦ߴs<޽3Չ ! _sƝ_՗C\w]:rht#1hJllRNғ(:ŗrTLqw_e k|{\FF ʟ_.{c[G//,78PٳsnvnnqKJ:>s h˪7N=&IbDsa~;RR,#RH+o:({Rk`|Mz}ZFIRI̺T㝉saN{2./3]$ZXtTr }s7J_= ˾Ek/ XRK/~ K{'M_1xENMT)_I6jJlJXXbJ)예 9ZL@NO?Jܕ.1:xӕ<^\Uyaiwj$ĂxWxǃ2F$gv탩T2P ?O0S+vٹ?8|4$BAk9J+|Wib~X,R ˫z|Wj^.B]ZRX@K{( D-BZRA14@Ssg3X 8 eOX QN ""% z,2'z>nNW7ݽ*:^JV  / EhG˵dvMH2x->P>[3/U9ٟ+Ub[\yxz'\ mgug-s+P[ в3N<JBA=ЀJ0@ġKm m|oe"s-8= .92N &sX u;zZ{oٷD7& vm;qdwO> ΀P-[@YqE|[,ĥJ ve&0d *tEgxc--L#qOGt WSa,On\IVJVǁ ^1#HX'LZY6ZLB!(DC ^\+e&u1̅Yf =aZJ@?DtYU>ϯv/޵ךXM/ARO>׾vzkᇥϜ2\tz4Jp)A0poοKh{|>$sT+ekT%zėp &$ 6$@9A`Ox[)pK#bÐ EBlb]l=P(%6 Hh 2L|V+T@M\w.FHI-D9(Z4(N 𚗊 &|[VE9,Z0ȑWL^NZ)%1!JAWb"g3c0,(xp|NO~?uѭgO}L!%i}]~p 7p #2 |:z*"Whh5hqu%-\AvFܹ mxEYָLhsž7޲yl#:,m $HlJвЊI+n٥sςu{D+H!K ԾGE0Z sVJe܀z `PP` ~L811#K0ܻܪ>2}| {&*ooi-G~͋nفe vD+i "@[- H AXj`  D Ǟ;HZ`cSWB㹣PC(7TW1[v \.Y$n\X~[-`󦁽JM i}wLmiHXJgV2:(R4; |d:>ZY*;o:!3% ͬN ĒAjb2D2lH~'*}#xlPș4] !TfFL-"FfZ/WQ01"uY'~q&^'~$CBqX/9];_z,47z_;ᆆI&:+|.}Eƹ'_ƴt{]'k̊W$eszc qSoTbc UR4גmNZ9"PosAѭPL˹BN*7N>+f'L7{i!"p =Z2Brկq!G|2 @o1+[bˤQ&lڴivf^o.o֯+cc}v;;Ξ=;ys>s,dIhn1'!Nȁ} +?m޷y{j֞m Z&oa˼Ls5Wz5FCt Y׎7ncTl:׎ڡǞOLMVZ{~~!,iSQ~ZiRQ*W*]eEMl8!TIsu0;֞8P jw&=fʴجpIƫdwwnDhy@ !#mgm(i9 /pԉ3>ʤ 8#%sCCS_0j?:5 mX9\3 eH8ZRL&mi~:sNvn󮩩߹mX\Z"[mA>7} Wc?xPĽl\x ,2ܶ}úO<_?5a5+w,B݁5Drj 28?whq|rw^IF68= F苶MSЧ!}y+55ziW=dsF @2N+rZo72xęHDԂ\y$szɰt@("D@45$b)K;=kIu84H///sA\J] |T&mRJDȗLII֬g)q&zر#u䇞l4M9O Z2UѬ{{A KЁ ,J}qqeP IqRt^»ǻ;ė[^dk\\55M^Z]b7_V ]-LVI r\UC0_.w:vގPB_^.^Luź{;n.tA)5FHᘭ#k,,2"2eYbדM,TmvV},PhLfi9FHޞc yhA\V P %HS(QkD i !`ggT&D׬6]8uC 欚bpl89[&/0,*p{ Q H{}ɣ&Olm{K'th?W/۷iez5:= D Et$!Ly :69H-$Ǐ'.AKgzq|rr .BR]>!ǟLJMў_:XDkV C nݼ[ I,F{>vSip_c.~"J8P@x&fa% `FH-i1Y"kAk $2#Lj9 wXV0#Pj=g܉SB>}7\'()㔤RN A!wDY%d_,VZjyD1G.˗/[hָb!Po mٲ%ԗsV*  X,i--A%:kHLZ{Ԋ^G;N(]jgS2ujO>5yq:ȗ';I# PJo?/{wugn`ӭfFگ<9o}㾧Ƕ}'ܾw~~y`{q~eV^|S&aֱd:ι #LZ)xxxzu`WWJݽZC#^Q `X].r;"cG1| րtuuR[B qxdŢ}F &J 'H(n eX];( zBP,5*Ã!#0 =lċ>X@87>J;;{zbyh'Xru$ŀApA{9[^ZFBY#$}I{vu1tSVV]#7ҁwɥť玅Cv47ybbunS;@ɲ 4"rI^F 0һ[.:jjbdC X(+H*a

|#)&&@-+J| 2AB1`O1)p,s2;OyC<_z:2]wvsuΜ9&ͭ6CF`iB. Xrsd7y^\Fn}G&4v.%XI'kubo;Mbj$ꪌ# .3--l|iUb-&f 2~'шu|VvV* $ :nS!'BzR<{~w>95TIHM,%g(J=]Oڵcƶ [3N6RPO65<>!YHZ2-CR*d*oIhǎ=)c;6 ]޿Z`"%9vR]M8{RRsN+1=^Z뵺"_sb"")YjzOwOHmaȾc'AfÏΖ0th4[Oc=%{'zolB̌ 2M"*(rO<;3u& 41J5Gx6V Z qSǐÞBIti~]A1,IV}]˲bSVvЖ䱰wO~/_1|; z }L{f,e%#B 1d@)GgL/AA$muȦ0qZ ؜]:u&YjCg8?rӓ`hQ;RqjgO9~ Ee[&{SH LA+Ƕ*R[w悂Y\ڻwC[M,S5fk% A2g),-,m:M`P+걧j.//כ3᱇~S{wDG>}qf # %MĀoiCžAH!ϗ gg@RZiLbzq::_b)S.fE` Z>r0-Ӯ{Jc%8!dX_|x?;G_ߨg?6rº}&/43=û=1F/ 7H?{9(;Ew "*oӐVJkW wwe{4v;q3'HQ[q|7IRJ+4xR}Y?|l#<:-R,+a?Ap0|T"H]. )X~`?GP1;X@Lu9O7ib[|{:s<4($fɏsS_O8t@w+R3gfiVk5a@ +x*z-}D@D$B4, ߭0M|/=XՋW.}핱#efɧm=33C}$~qhsZf%RJgVR./7.\%|ԅV;__{Wg+J8"m=Z9V6Y+ImY塱񾾾x?9I[n*]b1{w)J52[W6qcGXVya%/%WNrX&R$Nzd^y|\pʁɇ>x?Z>g3O~+'_n>?+zW9Yc " A-䞮µ`xxמZcEP# (gHmAЂ B_&mBâkőPt>ӑ}h? .Ҟ{Ƚa5+0C,rT׸}ŕKi{wUځ1@)d R=c>}3/]Z\bѲca'B$ܣh^N :p>_Z hv.oG𱡶ōe2<Z3k/_N]&`b0[H5\6TȞq+{"xB{ǏIfV/uXkCj%}xϫkzsnnarH/0I"[ej6:|DDKem.d~ыًgύfcuuYWh}ZeE,+Zdb%>>Mne/_+7.G`^pgJgZGE`Q K+9dMA Z"h%+^k㘻_~fF4^YUnϜzc菖k.;+n6N;!nhm/?k6M߼<Jj[_<|(*Z8Nwii Њʇ<= 7c$Kɧ?О!IBW_}鳢qI~<Y{xhhil8E( {ZY0֊8狊K5xeg}iY(-^ŋw.E¥ ։qγK|##}Q꩓3 ';vxL}ξg~7v,"c(V4?pxlh|3~3h Q6SϞv#r~|?"BYSo9:Y# 5ܝʻ[!" P+~/5ERAC* #gj @޻86.kkiYRcffo+7&)̉~6|ڻڑVl".+WPHt:HrGqPO‹(P<ĺ{H+t6σ v/W[~*cnZhڦC['w߇KG!@y޿\>j\7A`5JV+_ךwN?ƍ\W^XgSժ5hhJŽ-6 R-j͵hC!33 jeyVK(0:I}xū7=NiM8,#~`Sm] !@H`(g fKBaIDC< i;nz-{rrӸU2a,FG1ytdVJVen{=/ۯ=qڰ8ʥnv?էFjfR9bTwp{?]M3_WGyr%†7O}돾ח6.I;{g̀5]6A9C!&-Dz $Cj˹Q-*";I7B< u8<_/t 'ow|87tҫa5kA_ag- UP-{wv),JulFGWxGxVAwNDRlsI^X*l4)$nkIՖo1hҾ{`@|4`?'.:0dff ( #l 16&Q>ǯvGY>|0Qerv9]/vvvfeux$ZQg :YoFhXYJVPJ "Yfξxpv޽ lKcn쫯^|oz;InBJh h`d'_+:x3O(qInCp)6>m)ec%0fcaډ^f(;>BŨf޶;q)(+CZ*P,a>J2(F٬x aE^㥊  L5~;,4-Jr5 4Rx+F=o2mB!ߊlAQj3 bq -i$G ͮגsF́48w qh UE 4酹#kRD6[޹}cGFjݨZY8saǥ[% LoXW?%|bgz-]AШׯ];Xۉd$m[jQ"Ǟ Psg/:0^ϘoX5@'ZfVI&p+y] RC#ٕ儑V8`GtWg>r9t:[)m䝡]KVrF7/fd9yA Ή "Cݰ^)rdAYU jZI9Xk2ӧ}!oNxvPGjE[XN<+ }zrib+JpF+MfR3\:k|ʥkD_*sL3C;+vRY~|C jvTJ#MSI2ʇGfhW )r a*II=umn "Q?z`=ӫsn~L 34i5 ұj:ع9 DCAh%T@eXtې\ŭ埞Yz ߍuX'5?6߀! A_fVl-baO?2<6?rHƵoW.ETtZ'=&ƪ/& JXGQE]p帯OUoer=dEAr'Nz*ź\tAhf:vޅtǗf/.xZ:5z˟::weJ}gLDqXXNtk$VF9xɹn`dHD<~ʍgi{}}nfnn4hR5rqӅ  80ys{hJC']'/!FDdJ =)m?,#58 (#܆֢%[sKFWV^rᆪı PvMLVAᔑeEGGba `sU^6&Eȋj?/` zW>;ݞ[Yku A--<wQS~GO,V6FkqVDFV-J{{iRGX4Pe4l]?E+ f I 9 ZmfKa8QXR,@AF޼E'&''}V ɒ^|)@nQ)Pu}nu2Zkhс8qԟn˟/_Kʦ,gXȒBZ2[QZʂ[Jt{"Symb=s4F"{uvYK@( D$^;?a :2Hl72Qc p]AA]` ?[$3[k9"Ta:NJdt86117DbL]*tv vȟ_y9X{하kμɉ{לs+7ຕ}{b U4SZDa}5Dq\,Zkg9)-D <<B0}TZo5kӨr8Ѻךo\F3UNXXpVD:D_\.תUP+EJ&ʘ--=Uܢw?ɼc?)x \   {/Iw}lk={n;Q\I~ǣ:rl@͛CcCPo(.rmlmnz֘_t6msl]/&B&!?Npp` c%&m -}}}Ey*jZ˲̮֝UqPt‹?}e6/Gh- \#;8]N@LZ)4T]z¥v+C|k]^ܷ[@V(B6M'߿xl5,w" Z*=xq6hZWmmb.Nk4e9Pi;o\Xx䄅óz~K'+4ڋkٗpijPfʹZ)s3@ i8&{Rs\K7.U8qoo.\C q.O{PEfarHޅ,+"lBOP3|ALm* @oȍ >XHV PD/aV2eN88^;+OrA.@C<7o|گ-瑷l-9ݩx޺Zb> -A`)+\$(i6S!ᕥ MlHg澫U@ٹge8k#lh%[+#T!F[+ BE l-v.LH[~Yǀru;R䙽Q+ L덹]&nk2*oo,Pm8?~/ƆK_ ͙Mkj'/j)r7DQh1:jʘJX+>qAEyV0Jkfβ$EtB "a&iiߵ͕%Ҫ2B!Ber%!&ȑ}0)1zfp('|ȣjϟZzҊV(X.Gn&2BM՚z6Ofd" XLJKs}];¹Q?y~$طIkZBqI.DJD {@Ad(mOoasr+J`B=V@6['EgϹ!i86uP 5aD<R BzY}̢[*\ 73Azu.56 D[ w{Ɛ0 mz+/ {eجDlhJ+ks*4A7ͽycJY0+F{_KFsq/^G/NޏL0mk( Of-d`h^xkGqy0s ͬ}u۝[>eষ 9N-? h{ߗqg#ьqpr& zkjcRe$\@Anyb(-.=g ^؎"q jO;`{u 01;gY' 4ve푼uc[n}o[wdݔ:P 7!eȋ߈@t^{yG#_{65wn1vZVK Jnbv ͚;y{;0ܷkKo]?ȽǏ;>M`jϟzyA9 iOfƍDq36:/VYn+_[Xu!Qy!H< [>\z}7}=xصWYaѲ E/5 P%">>uGp#%˖mE[Π<OmB /;'E%oPY"r9i+yb9ѝNޘf˜-tg,-]j<ӧꍕl K=DP#x&&S1#GV뾬!٘ #D=vPv&WDYGȳ7@¥\"%n[5!/ & -h!R .C IQ0.J'*^.E#唶@,tZ /6GKuBL?T430lncSJB5AS+~j:kmE`n7TV#_|7_]0.,j_S׹b L9  E#,XA֐rCJ D1 L&@Ne`$M+Wă=^;hޤ路FE[flgAMJ1]i=Km 3Գ6E DP ԛ7qsa?KfzmBDa/mcBUthmxw{^-"IO#٩"}4w"J+"Z׹ތmu1 Ru[HԢ.ڝuG߿Bؖ;דּjK<vPAKoITLDhjuj% yKddKg ?1鴒Q?2:+4|j藿kc(2ҥˏ=`{` ഛ)C#]Ҟhw;()@B ]Za/*wRKN|S(!^>G(Z1#|*2NwoourvޯK2{Kf]V^k͵j|c9iv55T8f P H:y%0yמzL)rtk817?٫1=W4qlrI&Ͳ-/??/f1&r.Lg  ruX !*f& յV`XGJ1{QJ`KKDԶ뀇Qkwy7/ rg5 k^~=R9ٟ#vqu\)yza՗^u?J7O4$^:ZGJFvbݮ͟ԧ^V}Rhh8Hq|ܜ~瀙YVQΚkNk4Vk/ZRLQ}ᅥN]\Z"c6*;ߘfx'CFRdҊR#A?gΝ"eTC 8y_e3?j>%sD L$P ; D؋ wBV"34SM#m6l2 :V6Ktj\7\9}EV7=S.{P2WUEś7._-ΡQ?{/| :`9d6#BgxױCŵW#Dxq(,T`TO ^XIHbzK#:8#LE^v ~ɎAndw@l!&=OtTAEXm,'u"F49w`790p ܅gEDHd ؅a9o3.8s"S{cCcMNR<]]*%q$ *^\f8 Z`,!Iia-y ap=A"FsbImQ9R$4:W˕j_Yֹmi4}}q<4::벲-E4WqM;QD Okt9?29yW_z#?;~k]ɫ xDqt5y{ f'|Z>=ʂ"Nc+X8YHw hbY:TAh|`!!Fx;o8TQB콮THPk'Xl=zzu7‰晙敗N'plMGgNr"g-rxRe}|ʙ EɻEêKR89P-O]n/aCT2"" *z$)کOE1:~ퟻl?;h).g.׾?s@9bwmRwz58Fc4AM|x{}Y*쳟y}S{Ko" "c?;vO"OSכP24T1pG*}+*2Uf߲ XMΡ8nW3uzo yPQX&lbF>6IDJ7H hg AJJ hd)/I'zҢ ǠK <6*^lqG?G/_^MqQ\NnQdS bR5,0sJ(8ںin"iMUhՆ}a4 @Q18o pֺ R$tWd5ީ0AN kVXG__?7?aTjd l[Ѿr\k$޷wd:3wSSqU<_3&J("gm; ?nIB\{ve6`Olm,b#h(*g(xB`ûigTf_94fLP&39,DJ"RmYCiZ+w %RڄɑrhBQ&H\Ʃ(`v4G0BjѲ! HQfiUxR13}톏CWR\]+{O?Z{|@vQ-8Q^襄8<ه=×nHV:hHC3G`:!G`L qt8D^=#/&7廬hNO[~e+: Ko9+6޹IftI@Ju-Q-,1I:#YV"R@oZ9Mh=¡fm$&<7F;<_Sɥ;4;}Ba9;ƅ܆vReZk7..Յv>S<_ᓿ+Q8X& {] 0Mӵ5[@yGE"@6+/N>r҅drr =ow 6yRY#VI%©j*C$pwON ?z::;&ȤA)QTF`H^)#˲$"&f02wi{mG{31<Nq@;G>xMd@Vo2%^%ϼb"NJ[gA=RF;Sbn@<30Z_Kp?\;i~s?B 7Ǧ^8ɲ &^FY[nBqeháa?:26KJuU_gbuwp Qk8L` 5ĉJ[\" rQ2ƥH gX])`0p7h,P!"ၛ+cBPnƢLΎ,s Toވ%+pLM^x Y&?Xw=zo?sUg5UV@$8 2Q56L53q5c&h!"40,Hbe=!2 P-As2=EEqfKZś?~K+% xzv+B225#Ufa]0fݚ$xϷO|dT)HCyn0f' ] N"^θNI YoKQAZ^ѩO~on6]$6 uA7&,Ex8 c~AxT__f>X}HN'jrE;QO߼2~.I\N u:JQihx\@*(p*EqXDHm~uƩgߍ_סGgM[?y3=+3_"C݇?'tle4[Zge_ vK-.s`\ B",IR#G'/ ۻ?4֞zo]=g'pg?{]X;:a47pu{оǎbPj䎪-R: n< "o@ l.o{o1,fygQbjejnf{">禄CeE1휈snk[x {ﻉ[Rl>p{\K<Бx`ϒjk7\/I u\R̾i 'lsfnhpe)xXHڅ={W ն I23lzʕgg&y{Չ|{DlVz}/GF 1&Ҕv[yn EIEQ=zC^ڀhڛ%VP֤w+߯[3 .<ɓgE^K%;z4_3 W6iL1}²QzhRud|bܳ% 7Eե{[v {(F꾪tg{mq~dZj" %%_*3b%V0,/UG9SWb~ *͌7* _ne)zU6""=? )c0V5ϛkKA^sͫ׻37@ec52 q]|i2DʄC3'm"}յY' {հ07G:{G[.n,ܜyȡ9NEwj꥗NVGM8$&GQ)l> Zu;k6w&VewO aͽI#Ŀ#NjS'qAn>y?r\P6w;g_^>s#x 'ټXS*;W' Κ}<:~`NT %iV*,8wQhά `47ĥA)fIopk-; ehmO2zIb?';Hϴ뿙5;?+AxϾ}ΟkÃ>@C#Ve,͂3:2-Lߜ36:D 7OU4M(=FH7Z$ +VBM >K3@0=Q$i"A2u@"EcG Tp_o"SSV,ND+/E~'+/AaR;1BH#IYJ)Ni@{a-`o|a!EZ)M QDḄkPh`1:YT Zb4SR?/Mx;aiIRvN.ZC̪Ic 9w=3tRk7phϡ XEG = щ=άٵ>k9ԮEAbW>M #F#*0ˬ81`!foFdxH^6iY[@-s5C;ryյ>a1ڬS!^yhrA?՛;vxK[!h!{6,UJyEw> 6aj :nuoSwХ\g;|wɟ&ҙSi.|D9vEޱheD ,;EF+u-ۺWG*Zz.HDཟYiՔַG|Ⱦrv_󀛜c :v,ݸ~''NǃDS1J):URG6I)#C"E<&''0(""ǧׇo?)Zj .A|xZx3ds˹ q! RR KKG>깳OJgIR:|n@8ZkIkplh~e  ad2FkwmTJV#A=#l۞];2NiT=fŗΡyHή.1ʅ8y߃3u|k>*?z218ss6[imwȆ;J 5JA`x\{a8J1 MPlRG@ CD[wpt!R:g]/+v⿕ًpqoܩ*={, `..@㉦gD[FCL%ɺ>/D,cp..r_5YkqA+&") ^X)5<62w JحsLz*O ĉQ_RM5 IYEMskɺOr'C_t[=\v+T+wQfajԛ{/MRxfhYѢw.Pa0@"|Pb8 ᓼ9B =nsov?URh]C##ÃOg>'gtoɿͭu#t&y>SlmtXyAōnK&AqNMN^[[R/7AR!˓ИՠzS|Z9(*:TPY7#ۗl=%BCCBoO3kLT֖',޿g|\\2hƼg?P>|djtY$)iME|pF/®]O=ӟv:/~{Kkj]=:4|,׃uJ"@k҆4 <;ȋ*!D c*@f[C1MM֣a(wT> [^]C̢h>{? 4e %\rm߹F{Dމnc}G>xV('im[:cAHϥsܜ->ɡ P2H#SWNӀ Y{fP6Jieʳ\ ="R|nw"ZieY 8(HA]ÞgY-q+WZ|jdp(u~f.?~gIdfs+JVpfX>-@wHy珐nˉ T=7O`6m`oDݸr-7ޅr aQ}T1{4 w {3?[j#NQZ41Q>HNw8UUp+_®}_=g--M)_~Z٬Uo~jy|-X[kKg8P5q gm7{JWA^Pk)Ma>i ^·˭$|Ne.P`()*J Pl#w(gYzf3<XmI[Tm ʝNM ~K2Ɗxzy>_?4q?[gسo߹\>;fTqcWt)kr<7o{qnZK\*,̛ma:$l5Lo|q37 G_>Z4;K^[Zrͱ:?0!P&qݹtݴ=gב]jCI/^=ř+נu xRGz3} o.N=q}ʲ_j #R* rާY8&ޘvbn'2J pv9^zfP|qRx%!r5Q){.˨윛 T a4jsn 2|_XYϾ]*?j+Αd0Q)ʯ jJZZ#v^x5WKӎ)BQ⒈,-t:kO$=FvU*% CD/bw^e"CEVo,c8<._:W^H:LHف~KtG\XKRWgg[鋳qKPL5֥it=L`]ht%H({qgg1:8xUIûMT:v`U* N4#P#{3^қ2 ¾!.7rX&Q4wl&^LZS@x⯔QK &&*mJ!H:0Mc$Iej$xx@&)[l +r"]'~B x+e"j+ePa}+GCzR ]RE,muߢl*|7bm:|փm.Mk(tAozN)2N҂VQтBxwpڸ>c?xZwey^Z UTH@> #k7%="*^XHffrHUӟ̾A|}Y%;l` U!ksE07*3ҿ.6ڑ*X[m^t!w|ҵ<+S_zG_^X\^Hך''&,{ƙWwMLLLj(:|uyT2X/켧Ҵ/ɵIF"$>u )s$So HSoYMLաȠEC ԣlG[/Zfq4=w}\:00i'NPkֹܺv659D, 8M @{mYq@P &RF2!-7u9ul1,>O2 T O.\z/,\84"$Dk$P)WFM%n6xa>{MIb;`rtGo/"셽ڠVسk*z{p<ĞŕEL3z|IPAڼ o?} -~(;pbvaη3@8S&nN6yf67¾hSkQVP's{*-EO xz#"[KrLۻĹVX'ZcCq\+C̺-X7k4z|'򣗯LaZIkh2^"r`v'& ’IZyy稑E[NzOdCmp-mt_PoUt6agP%*:w,lw;׻@=B);60_!`ֺ6_OO^kvV6񜹤W3eyֆ(Q{ʳ<EZ?g_FS'x^)Ai-J{ycvk//vKw]ͳ2^ s)DvC:?iwIO=:6ulڿwn=pp CL9v^f콷(~~1I!XgB mܒHlhlpm;JoT5. 0u`bo}{_]k6.- >Oy)Ru}ͥ߼xY K. =zHZQFߖ\uZQRHj[ŇwO}AO2j>nI.a' *C{$N|B7"tK뮓"89M mD p9¨Z Js/1נRܧ" ycSs}XWkY ZtKA}}2:Y8˲3{ 䭱̆M-`,H) p@0æ(PY9W %! eyf% oڻ_;.jZ􄒄+>@pԩ&)I1׿gOώ?1s&:s>X?FJJ+^XJe]+ v 塀uZj lw bERP5j錎 ^ZvjoƉzS. (Jm0T;*_@,ϫ*\n e0(9 ^|ҡ=jeyee!yn ʥN*$$R|{hh#R;ifJ}#ᡡ,PJs@44?=e wN5<88<8xrf&@k?q^7nw' m  ب}8R]k\̙6[њ왩H,n^88􅥡#B,Ҝ^ 2"G`*6at0(6U$:x&"Z[ZYyk*d`Q9,WԏwML /hrk~ƍ9Ӝ O( + p+u̻iz:!]v,y%FCVAwY"E_|W|7NokX&oN_=_9ԻPƐ/w|H/>:ӶOԏn^]DJ F-|5@]9uk{ؾ{nz?,B#4pj}Wյnk0Hvn^nvnMQAJەE>*O(.dRPZkeA)W˲>)`Y][$4Z+py??29bC,d"٬~ }^ukk*s4q40×^q[vQY  L 8*}C# >g>P R f@D@i=]{ ݿ-:궎Mm& [k6ye}N n'I5.;F rƥ\ C}YMxy{kQqE6mybj,r8H]"޹m'W@:6Q)r_X|e͋VnZRLxEy&|}j:p9l&yމ2 17w~/Tes(e+ a3T(h.-w٫p)!kgdMgWoJl& U-3eĬ˕(>͕C]w0[_ngn#M d3-")Fu|4b3kgfdetqO^B7C#p٠@r Q'vdqemkozW~k_zp.\:a7_~e0^8~ kta(ךhwVQI!Pζ|iy;y^+^zkt.(@IA)"޹<ϓ$L (`bt;n37Z9;ixO*ٛ~wR>]x3%#< *n~ˏ<.ͲR+P*i*r윷@ _r,T JcC^r<+O#4x>xoA<y/1=&GbY&, 0Pߎ+W/,8W~G/ WAt@i߮;C%E$ s$2)Dvmclt-\m<& "V9o3򝟬ROzn.#S-li⧷[& émE[SMT .) Ÿ3Kn <=@&R<4z"hX6Xx tx;3ko+(Zw~wμϽ:jypPyP(j5=)um}adVVDDnVv$&9)LLoɗ/ ".X^y;Z4BNg@ac b =/|}GQwC %#ne7oΖJQuO=&~sƏt)2:!Q {f@3_XXZxɇtQ\@6E=ʝo UPUv{r-{IE'JCiHA`lO˯hBA7\L;YsF A9r k0 Vk]L({7E;z`tcO*s?UCÝ.8 79k_B`^OJ{'t 2 (*vrs~@i R٘ ϭbv !{IYO8GT0f=56uې`@n8^[>oCsSthKR//[mP:,;=l D(G1d@^1)1!D JbD2l]m~ 4|cw۳oH3]rq'YlזZ+@0X#{Vفڽ{>\iTc~IP.O6&vSZfLaۇ3 "(@ͨWA$&fVJBޚ~'`>rއ_y洛" {H' H9Bf!0) a;^occyA G}^||+ODJ}0) BD&I:Z9hӉʥf=440] g E1hg>zsu|P?H}XPϹ֤ZVHnnNI;UU!Ե"@@ Һ^rJM3W/HHH'+1QBт(brnh L Z:G*(<@6"S2Ue=r$?{t]͹U$9G?/d>N]<mi'<聛7O~O{Bɽ񅼙)4e0 %ݕv@~+/﷭n~!.!ڞǪ0qΘ{w*bK򛾏:P6fK:o;iw:9\JW>P I6YD$ uEZYHh!MN\rȽG ,Yn` DgdIvkfO -RkQ)*K@+ ѐ(@̂\R.3q.ѪUi]YeDdh w<:Kduڗ_WvF{{8 ƾ;IDWH3N4r |q:k=v),,[ x8t[F6=RH޳wdlą|}{z鏾\M++B MH?IP߱~|ŗCM~WыggxÑKuC^ u;5hεZ8!%E% 1 paa'=Mb7t;v7dz{6lh%Xܷ+vuth-8"HIJ$ $Vt0lP]qͭO=?w~6jmԅFx}h3iiF ?PiaRέ4*SF' 83 6yDC7Nyu_f>s|#hhMzуW"rӲ;,^w ՘&A AtXߢ*!zԍ ߖ.뗯 7Fdo[ϷXm;G|w>TiIH"iw/_^rGq,ts0FDI:ꖭC4UBL]:.х .]A#ŁA6֞؛ t;>, RJ:ŤcQ݊:>?={ĕr xҏmR+Fit~!\rsϖW* ^v"w۷..W+)ԫӧ$"Ig҇Ƚ)Lٙ RԭV35dA3ⅆZjGqgҘ]wgY^JLNR" [Ԙ(`) -9k9!^ YI@.tqݽ} ~9uS R11Cw"h ?2@,,.:=TB:+/x?}.Ol:| QVu;oPJvZ,K !}Yu@on=p\V-zzfYcjL N[8zgۍ}׭{/<܅O_qNR5i47uL4b9yPTPB3m12:la ۼ+WZ:tY)Jqrs剅JCI0 x8u*iߴu0ȹB1+fj!h 0mw]w{}y 側4ENy̟ٳ>YHԣޫmų'kKJۉJ*{3:6 `6'oK)d!!.1 ըZeTd ##ѫ6e“`A>C R>G BiQ$iEmC횝1&AޛO]}vS=6s0Q TJF-};mz`o( jV!1 VK8Ŏ=ۃ=7'kpIHd% $ BJm1YKYul1^/df:Z?wc\^;$5ȴu `h4:Ȁ$ډ<Ѱ+_M#(5 M>s*W^+U?]W]j$Z "ЪB>"HHܺh =K~v@Aq|3+nfKy[P3 ~787/9]}u[o:IM<6:P(|ߑ0DZ|?c?7綇S.{*]F 8BRk;y8t1K؎G?cbܶsئQJ>sɲ}_ ID"sfI1H29  ݻU;՞'NSO>S$!>>6>:yh~ ^] b"&!sƁRQXڑ{g jٱ2(}LKq#҅zՉBsTZqҙK3/56R. .0WOԖ@X^pean^{dAy?PʇRA.7ؽ~>zLϾ0Z+ cr><+;tộEjquIr$3e479߫5"0f->Ԫt- %s @¶R68dƹ DBXt%]}O{CEJ9vf􈞌$6hn2Ot-NMPa&f8纭<~kv&MlH :lR/\qK#!+J%?:kUQj$][xEXl( d?i_JtS=pN"k5}MZo !t w2;j4(AĐy}ﶾ>J}թ:WzhJBn@)H{cuY)?wzcY[ͰD#9ϳ;iޮI$TV'qjR.@1| )aȌ !tʙ\zlec#Sy5:poin\\t9*X*=K/^XnRʾ ˕"Bͳ_ 6؞[H"aK-[ hϼ,J}(ҭ޺/|f=Tj4Zƕ+Nco{  rK;LJoڶ3ݻv/,Z02խ+K&&PkJmDb9ԝōjw{W^C#BZF)R 0~@3CV4ڢVV8G HqّΦ0iN|Z B.t[ZuL^7D 0zDBJՅ]wővr!]Oz=JʜƆ$ =1X.JJkrw30BU++ $ZK@f8vO-DIHIlrAM@Y9:ti#N/~+[9s MM\ZBWAV#@0pS_.i S8 w`#>x4 M53ٙ٧]co/7woߗnVD^jfܚ[BRhGft !F&Ѩw&;\^q:DkR gOL3vm_>w|^xFJ fҮ-r((@lG{`sieO[۳t7J=za'^>#i$ yD:05BHjDH[y⳧y8HQ⾰7mc۳cĕ˓gk@B0$Asry @t]GcػG1]ج7\,KвH 8JD3x2[pUs#;//=?q/(lȑ<3qP.yt1,uGn%z/n0zG*_]uAɞ! fbp1uLg#D fЖMIS혳M')Gd2}""lv';Ll5p"s 7Qs5ޜڃ#o !e0t!9RhoΤ$=_2L޴P,jSD؅aht{2yύ~4ϸY狁PWM]p[mfHs.ue: NgՌstڞ?~螎iL/\=HY, kmRT٥RuQc%Hހv]i÷  ߎLLØ4hyT;jh N=i9!!|vʙCǷkW[U?: 8 _0VC|LbpS3^>B"pN=w?pdG],Jg' ʥsy [GWLkqO<}3^y kr,c@涎mg7ŋzn ryDL hxm[qy3V#$<:ex2_.$y㍥eRJ\:wᴙ*O03vVto³Ͼ6׉u#E߅ol^ɒVdZ3>Kn!D  !%PvT& ܃W~TXo;],ok6AdԦ~[ '~!3nCt#awø'Mu]зx_9LܕM0v"ɵM$2NsCՕ??8c;pUeGD!-T_5/_NJW;m& Xi;~ృ6nCklmݾf?.w !E՟zf;191X{/<;z>/waA9 P&'RX*% 26)3U7RgW){;>}wٽ7.֬*UG2zomaMtw~۵3Hrق qlS(\,1gO4A%UDW*#IesmG p]WUC[@4aI~K@;8BbDǔEirr<Á~(4(:JcCNsn)/0` =C7m[4GwoL)[c9[(J2Wr)<J'?@0R׳eO~xDʊtgy'V NM' N_h.4bF1DoS-i;|8PkoJm%_9}9r9t~Jyaiř/>4ܷwhؖc{Jvvzz>)72slν25f:E_qs &6H%6&x7#/fFܺ K2&'*KgMv- ͇nPӍ {~C7D&rDqԘ9."N)|]c]^{7.uks:5&V5co UD`fcLS .IZVϽG:q=5~K=ؼ];`+0#oN-c#JJ'8ۣ6X "Ӌ&FF8f!X Wrl;?v잭yիFB)݉/NNoݽÛmmVt쁣#^ʃccW,4~ ]w lRdl+ /yV!Eے };F[=zs9f&![VRԢINN @dd, S/h^YɶNygQۧLwQ"8٠xR; Kd~aDtdge#Fʃ2=❹P lI2S|?N_0{byDQlcWIi` Πy ͥ$ڱKVZ/4.L9ݞ_DKJ3Ṥn_ 6_Tԧ+C^_ӱmKF-huuì,lw:qKu |t)z\&XJk=r=N;\J)Oy#/x߯! ,kӡ-#b׈TŽuI۷lʅ::5͇Ui/k#reKd\2Ycc˗*Nc+__-v7A :Q+Nr,KN̉g6>pP:{:=O~ I7sp'ѓO?'/N7˝rz%Br'^Ͼ`wDO HzQ.3K?;o ]bomc>Yo#RZvsj7^Omc瞦훸rSNv;bƚ}Xtfb() ʲ -򑍬"GvFLW.\_Hش;mnu`@ԡsyBk12y [4-'UX$(Q,Q42 + 4VuطmfgRXԒv#b|a밿c DMnE/N WR&Y )4Cp) cE̽)7kT̂q&qgޯ%Yd ۍ؛"n :›@"b8[v㴖...۝B"-l;vH(oRgc!pFIڹc{XDXAhk4MNN93R/8V(JnG[~yrk|!Bz=!6mSCvblRW]oG>3[2nXe]];HlNs|ۊXg!svuwl ~WN56PR5U l{hk|xQ@vNj/Ac} oV}dR C %!Jmq̩ ѕe5sӮUtnT[=2"/n_}~algcv,F٬t Ԡ>-DNMV1ؿn9ULקap8v.ypbpt<_RE(`ӦS{9[tq> F̺9WAp Ipo]Պ],tm!oջ"Ghcy2U03HThuvEWg2 Qt7!DK۲7~竧.\gPc>[_}rCm\YrӄBcdf4uJ:k+*愒fwǦ|asx|k vvO?fjӧ.v/޿Xb$% E6o[C6n0~ }70Nt}w 6%*%'UI?gM"ݷ!孭>[|u03kU5 ?V޹gG!:^>_n6+ӳg^(;8o%k6cJ!=&s8&mJCb F2DM"I=iLiDރ/eG?4<83/~J}b,4LKPw..D`LHSΗniD|_٥ op|Xi>9Oϟ;X$+sشxS~Uk[ Gۛ=F#^NpO,.Wϟ<܌נG[E*!ohqyRmb @AI$),`/8 0qܥ1#[șRF&,뵪{-\y_nQVl:&u߿݆z?tnmzlΘʳyRIK$fHUtdYo f jGܗ3ΙOW4_qٳfP,m!\E;d]ppYuNo?,=v;Fzt:"N;|TW|cUɷnHzoP?tK*] ^Xd =bNYcL欵BBZc-"B 1ihoW_s0'^ܹc3 A0[^&!}@I3eXkR NL,GG?S?d<4V¸9VJ^U}/wxy躺ݨ:U ;;0K|.?qybyibGJ}<4_QXnta{6syv3K.y :l6<u @瘝NAb+3KzaZ:z2ۆ3J'b6.!p)Yn\vlMk3SXhPN;Ԓv ^ }ဋh\[tt I87PJJ>…٩dy{hxNݛ?3z+ߨF'@Ŏ53eAKR|Hg%b98#9דwl p5p`ٻE{iH-\ +MoczhM2L߽&̎7Vk)&v; F9($ AΠ7vwR\wMLuor)LVSښ->;ԗSqtmc'n;hkٚśl+JBy*sF|ˉ~G|_ڇADTny \WZK7>N+W ~徑#=@>{jK4g&]E=?۰pW!W`5VAD ~s}޻<75S.sz@/ "!RI![͖Z0(O@],mټRY<54<E#Gݴi|rj*W>t抡=__>B0 m8g;Xy NE(l͖mL~DJ)WffG/o>;hM` 8UϮ,~v(E;ͷ &SQtf]1w7;q= _SaP\mmݳןyuhD/<gff^4^9[m(E(PoK~)7B/b\|V; *-xԠ/ٹM#T<]GGŗkqT?䋨Kl=Uۋdy0UD96[˭R68iDnЍ0R{7$cͪ4(B.ÒIy#3[笵Q蜚_X@ן |DvΥvl2'z)vz̅Fk̳p׫͸3Ò)70?4<$DWXjz @t?,#$k'}K}WRW;Wγu9XAJZC'sK@"CXA]}i%)=/1.('Ny}GXQ=r=wF!=m(B#"ut*%;uѭÌuhP{s{=];>uzfah\Ƚ+ڼ}:b/K^Ȁ鋯/.C(Th '^ saZ,N|[(<./kHtSIV}2ƹ(NWP!CNڔ5i8Lew5*^uLE-\k?S_ڹ/?Z};98t`kɴŕ{jS#[j_ g t#\ pL3 sĽ7tg3jz *)]W`mDnꫳS#./L R )B!x7U6tSs~^AWZ^{(jE|&\Mi+" ; |a NE-YD'#KfA*ҟӿwfVL{]չa]cHNLvj&}#'f~_NYྃ#CF>%3NwӥS`f:Pv4S«Itb7B Hd! IߙTb lYB;l8N"э<@R;>{rw8wop>'~ByVE bCƐ1@[EZ""!%*z*:|ovT~Ct!hC $T]|iυY81u,J9ۥ}wܫ!S/B &I[ ''E$cWb%$pd:$DL!=qiR:!шN4`@|.W_"5rZJi' ulhxdlvjUW^QظG+SN˓l'- V@LY@Uv)Q$F]kƿ0Ԫܽ?=s@1&T9(Ja \`wR(v$BC?vZ:X$D)3 b"zP Z7C v`ox>)-l3Q椓@KmR* 6`AH )Vڍ芉+Z):\'"ۘ^nM.VN VRF`CbAqKG<3k*B߻u}/ԖE%|1\r&pK#JqZ t$DyAJcsP't^I s 3Wg'^ĥR)_'bMtA18r!A훏-Ձ?|Ã_yi }~蓊?{KO#Ykњf#%!Ԧ26,LZaQ/hN6ZٜusB2bO97uRx=H1hRkn]sԺ[oN="X^ HiM 8'=onS_}֑Ga* ]_ܯ> "j%a (;wj?ʖ9vb9xSNkTxVijs|=rC٥+,`aź~jj]7<3zVJm~or³/`RH{ q&pMk}W9$Ky9nf4΀faU @8DA"B [T>To=^iLNM&ܥ/|^9,%N5 lmwzjA.&Z b$%[Zu D4hW:h9+犥>\3nev E.Ϣ_^ kv-C ngǁx$ח΢j` p}[W&gsKSs (p@'BS>R,;)*mh.'O.jP²olWO&_Lm~WGr+_|RDm}%P,TjfUQ:4`ӽv}iLpʷo/??=!aVl8%T^Z*oQ xG<:7=}qj`Mv$=R;ʅ>[dR]9YwÊ`]1 RcUHFQqZY/v彿1 x6-jֽ@5_s[r:y*C8(Gⳗ&eb;&\DR2}e lr>4M=ғ2TDQ4FFc,ZBkN':n=rJ;gCz*;'ym¢*TM8v`odk mLD w.S `f/uǓn!v%d7ycIJ+V &)c2:Uy/#n W>];w5j@+'-+mVw1$I֚,6X'l '`fvk78IX@+ P+7ll |U8cmrf3nӯ|? - 4 6u$wFߚA}'۶먻 ,9A Mؾk[ZMD)J`_9=Ix\$A0bvL)[#i^`r9@6"B5:ohH#_lpFJiڻosW;CHڦ2,9 q#DV[B4TJa;O]{c <8QKp O 4P۲c&?an-PoPhr }iN@ɜ=j)Z@ۙ&X81 hkbFa SehKqĘO3&95K*+H*/C*%c%4.Νx N?bTi8sPhCBPvw Aw aפm}c~++ <a-L,c %p.`+!cR e%vƈs:W5aR=!`YO\:0;F֙쑃M9'4T 2,/+n l ;S=:L˘.ƽRncb"Pdmn?ꖒ/MM;Đl:/O6yq TZe9cmIڹ뾶Y#*à^ cwn*fg ~=o.,OJ2dgO,I{j c%JTۺW(&_ ay#Cdv !6n\~[*qf㻲k&l]&C$ _ •9ʩ??tTL@D^ry:%چND(0JRG3T[M"bǩc c_!O*ccK̼a ٖe $2Sa_],,Ֆܹw FƆF+4I<_9gqߺ 8ߠ"w`Qw @OOcoO.\֐ť7}ppAI{zeak ,romp±'ieK` HalNvi봴ex=''T ϡgzs)sZIg4R`b!KM[o{$( 0gV+>$5.xrꬪy;N> Z;.֛1+Z[]Y75}Ze\{lm:s%樠9ĎiK6s(濎IH@ 'W {/IDESOU?y-5Qi$E򞵦&$)X L]o!JPz`gTܶoֱiO@B1;x* 6bXk5r Yc\a)$gq Ój 8Zwu@)HRZk_^*gͣţXYһ~᷃vKo^Z]NB%ۚH2g!8V/?Sc۷U[;X2 3KDз}V݉4VڦQڏ~C^;uBeT%]FtQb~̥pd_xW^{ir'iDŽ.w ZoIfβҋqHmT/5?t鋯PyB8)ѭz3sCú4N+fH2(KJScX*CI)o```aiQ@8sF3zW7œ fd~:1$r<}#α~tt;$emBMD]AkE^SS qij̹sC{U닫Y fuA0cxV"[ ! H)<z| & uGA̢wzcedL(hIٶvX9s~S3?zӗj][ PW0e? 9ZW{:_'O@t]>B+SN:gX0;wߴR A^Mg-;drȦXh:wMbK$AK[i 㑘87Ӟ^v- ͈5vQQ  IffC>V8W9}I?#g'.jG_̮ ߦ R֊#R0BphU_uxD6鍣#\-?=ɕ1rWBl-7jK XN$+V6vW L19eث[ם]V(eXifR')s`Vں8|'+30Hbq77q+o F~p [s ~Ow')ؠX(lټyZjk fEv0`rD[ ULhW^BkzMk W#X^̮K.clS;6wq;Qs@-V{֦_[>K_D'aG ,y2葢Tcòk%) VB\ꛞ C^aX]X:tȱok$_/ ?ɏٷĦ$P '+SKͨ:?DsbQ˃+|M A$=oxz 1sB_zX,ՎϡmسϜ~N 涠@p={A'()S "sZ۱Ç4=pCr~'J?P^/r0o~kdddH j6IxRYkl6j+j9uRX~g1}+qm{:ApPjvǙ)H&8OG>ȧ<\*k/P0dɮjQ05Ο=kVBzM/? S/=JIpN#NVK)|=D$9Lq',F:k Xuēsϟs6;'/%'励ڧ0T#rд+47`'!#vG@ݴc>vT, ٮKD軡.myrܓ/ė)6"_t_&4&ok^+ fdkMZw[v߱o' Zѭ:PR ]S9;1@)#1Cb7Z-BgO[|ٸ[p:L6#^'>5$i%TtZY@[>Abg`o-J=rJetl{=k#眔 as[$eQ@G>1g]K묮HHq )wi:Pzfu: |SW&&Ɔر0RYxdJ҉[3+21ȓ!3N[X@9tMԿvL[G-;=xW}e'\6GS퀭GV.N1~?dϼ* 85s/4q_'@rq=|R)VFgYh6X\Fk/U@WY礒0YZ哕94;&Į}ʉ?vR~UL"cK@>cXD1ؾ) Is1T\c-ʯ =v=vdhhhevnfv-sb@ IM!,Ujh[=WW&^ \{z5Ow-quOV`ŘZnp 7(/|@k/ݼovѲ9U u`kנ5@B(889mD9P'_ؘGCXȾ]9rAv??@a>`s\ ɷL^ȥm qcmfkD#8J2טMWq^'筭RXs#ze;w/oHnh \0?:w ՠ $6GU#z[_7߽|.uNpp AVH'#wxKQ T[Wv쒝v[-mڴeGjID$Iٶ-J^oN,Jarf %\PԌZ m}_YPjn.'aT) PNդ?w'W̰u<8hĀؠGphPo4CwRi`p086J4:J;Qfec"fԑev(󱶎$I$Tm}&NȸN8ٵ+D[ZZ:{f箝.\x{T.WhN$>-\ʚ.zD>s?ڴ}|˞-'??&pȦl/۹C6T \.MO+/;k B:&L֨&f7^ESя0ޏsXca5@D.:AC'HHAWV4AT3VZ=Xn+gf1tכJ}NJڟ3^{}K_:(\ 6Q\B)j)RD,elĹ/>_97=9;ejհ<Gu'H)3.ƫӆ:Z290&ބAd-zK i""NHI.H!Xzm+ ߧ'0yd.?4< VjG@.ܾmDwz !磓:k[fÀۿc衃[=qqrg` >LE!HHѕO~d/ݾ"iŭ8stg98nI& {2nSV_o2f;HtQlV0\ݲR ^()<2jda^L )!!%0qdN?I%iA2Gmmϼq&J+O'>\mU!u `Y\/-TM 5P6$H=}ay(<]I֠uosMy;_A&pfDDPP_ƫ_'f/_R UL zmKѣRx<O£_*+j 9 56 ᇞNBz6u PqLDa>B A`fcBA KrьMb,`patSCCy1̩'(l-ULI 2pp]\/4.8q>Q$c(iw tJǓI"YΞܷ\ب-tA!sCoqK)$&꬇{D)X_ݳ~hV0WGZ9dt399+UgBB(tdL̋g&__OۑLSܫs~'J~ &'{@ :&|xOuj~~ V,['X`ar{ H Pm9=8Տ,Chcx}-V 0DN͔2 >u0}Ai}ipb_-U$"0D,D& BHS%HJB_GH`+l{Tz}3zX\]k-7S^>\ZJb`?9ӓ z d [vn]Jf]Iz KS{7ܝchA|-PCw!dAèжS0JH*1'joS:ѡX g~ &sͭ]7LA~v] 5cnD[=| < udu v} "+h>jDɅBXYtvu믜JP@-/$1rer\y}g.|^HZc!!HuUⰻ33i33;&AfDxEo ;6l X#a=w!4]_ )ˋw !-mHPoP_"t^Vw~DunTzC =۝n X RF?_}#џc!y56 Y Z˃ʷ`ԉsbuڒ+cK.]|un?.͢FyJڊ;IMW*M0<44c[/BZg|,;+It]H9 檿+f}ҬԥE/uS HÆ"`@z8,]Xc#[>ܿɲXVuhye/Sؐyڨ\&U . r08:=zadY(vFϐj<}&MV%"AؙhqZ/#'*uQ#ġmrZsWMaH0p_ؗ 9Sp8o#g.>?\93SYN*7S FJ|| bĺA)nB6!"o׽|]*;㺝I״}+_e{BQڐʰ[ jMd -Z5er w!2OW^|]Jy7vMs􋯊OIIVϖuī7%|R1`aB  xw+NjU^wg 1c^Y^H\b&/_ƍ /7mcܪcBprblb ȯڭVcG(Z[hFo_WT4rD'uWjjyxN H)4PZe>v[/':gzݛ{G@ b439ˮ.8W.@}8v.ײ)4 v<|w豭;vI/ۖ 4N ѷo $-PFZs.Ig'&~k/,l'zRoR"(1; [˰Kuy㹗?_?x{؞%"L:74=7+Ƥ:گo$g9(#J ϓHf0t`'Ak]uX)ɱ2R*~,.NJ=}Bq4=8qȤ8療*@q-α"(y8A-ud$ɕoVYW."#mT+Ϸ ƀ#~zq`vӾKMDQI9fS ]dAdzr r)fN; HBHᴆzIRqS0p dL.CEl41ʟ/gfn-ZG$|_ڿ3?gkuEFH![&!r S A&U9fWHu: IS,CCGBa~aTU>>?Ͻ Ag/^>{n" \ʅЗ"j6F˗͡EZ -wxO[ |126ݤ{qH4XJV???S×>fJIk l !KBi G> ,VJCkuDH5֮P{ .e=VIj@q֩۴{ rypO|'>?|싯>zRݢ _I_t^oVs9YԔ8,) pԕW#wݯzH ')ϳ6A\CDUZ80 T|Ih!NปZ (IcA66H-S2"nۨ8,1J[GSp*VmzJmS܎1Pٿ͖3oȐc9&"vsI#EXjBZ0::|if`xkv1V=k9%=P$$BoߵɥY [=EKM15_Gy$IVPJTccerxmKS^"lD$a" "h0`/qXpl,F`S{~0Z*mD)s9nrqHkA6`=;1FkO4P UݭRi~GGw_S/B8X/7z_۱`kV},ҩnuE?Jer~P.>uFvؑb\>lsRm78J;q( ssPC;pͤbi|.B( s!\n)|Wg̰lAX[jR\Z:?ۙ - @NA90 X|_[, &,A L8WSdqwicspVE ںkR.7|jܳؕ/v4$E&vIr±M- capS(Xq VM4ʸl*thl;OA9Gطy8AY{) %|8:6{D02:C|]YQY'piYKb_ov+#O׎$R12%6ϣvȃ'N]rI}~hGK+;c*pD1QyKJ4ֺX˧&_' CB=A‚f]οtb`xœY)'D1KA|4:xM㻷M` AT 9wON;mǟvas-' CDSI!"gmJHLjk32*.砬1sJޟu]Zk}ιc 38,ɢ%Y%rd;vng/^t^Iv88؎Gْ-I)P@Ýϴ^qn $@IWuֹgfZs}c i6轵2u|l pn$1Y#Zk QRdef6D"^fؗ|tµsZs<u  7c_RD@`bj`i2<5{j-OM<އatm5^ &ΓRXI4|_Zmo4M*F$/t-wԇF̾[Ʀ,XLaߍ^dzf :(e8gg<_jҲ5}m;;w_˯s7F`Y<,dZ="c<:sf"Ԣ'>k ".JDoy:PAd(xށuP]F"?".@sr?Q9Ĥ dڭ +;-V;vGA;FȡنUbs y.S+ۧm1 ryY鉁 g 3 B 4O:4gw[A;P'0H-G>w\uhڡb}ҀDQTAwe9Ź.H:m1HEEli f:ʅ$;:+98෼7 WHm&йd_]O: .sRp] Q5b$A"LU*Ȧ"-Drk/ _e_N@*jT۔\EvrMZHăϖg/?j "٦)*fx.! !2c0,t%8 SCӓA<`Ɔ#ͷ?mS;z73=:"e8M}虴Ej+>9Qܷ{th<#(DdhhT;JJ2xIa r bwpjfJP @imH8ZQu^Jkڮ{KƁ֖>ns +OWFaDP((p`p|dd`) Di؃sfkȕ7}lajs/֛i/?HУ'*,heBa,9K:A\{nl2/lsuR ^א ,N/'T pjc0Wok€?/,Wu%d*&(L El B 2qΙbI@; l50@NK*^뀮;w@/-9XU( zyïJVJ hq]`J:hQ] p|?:{lIEFAC!JAsO~'?Qٯ)(^Rp _xT [6 6bvBZ_h:"DJD6rfH EBQEe_&wݠC)fpF90TD3 7떃7?~;/7. &[p 3l *o g|fuI4U@J)A.M* ( R XF*^PV4 LHwFad ;2Nm T+DsY *!ݰKM ֣rrv1^v]N@Z:sl ΁Hr!z7Ӥ+=p͝ճKC%e,0JP,$V(+vE"AWKC5ܬ]9`uS\-mI]iLʕ./(Wx^%!!8Tl FA*{./$0Aϵ}9meBUv*I^#+^eE.eC_` p?EB0"v" B$췬( Vxw]ѠNӿ˵V[Dފp_TдMWd''&o,'' zD6g *;`싷^Hcr8#ÃcS{H6) 6P+5+Icblw64FL{\ ><сa D@%7$L$ۜXD}y7vz_|-S]X\Ϸ+>pvq1,UHF0 oֱffv;1ז-Xi4;<c>sA9jT bͿ mٹoX7O-g@ۻz-σAX)NiGMd9 ",&~qC)݋A/ÿ{A!m<08=twuv;$2`@CI,OGtz jmutT. Zf F~Y{bJDiH6JZMR^D `gMVbP2`::)+Cc: l\:1f/Wnޮ&ˣ;N y}&uHj V!A3$ڪ8T*-`螟?~r`0U¥dY,& }HVjz0FF Wܶ :z[XA̕!Dspǻss҂&)+%jQq^Ȧ={) `~14&Xf*ЫJ6bJg3g޴\*6{CA@:c;TZtH"[^i|]9O,^S@ɉLL2}w>aZ]#}Z>썢ăԉSS/tH,Bj]K :x۝;Ϭ'qUAY_]QdYF?8OlZ4Mqbb2ͳ$((Zkmsa Ku+ޞ ˆ bMh*S:0 7|ч+F.zQmfpo+_?ݒb.#e 1*(.̴M35:s|% GR9邹( )Dv{԰!dJD.` ( oERV`Ã.ȳڎ`mG=Z8}jӛi>,J4XiK3 ϥZv08so؝I~;ϴO^V, 9tɲv\ ¬@Ep*ОQ喏x4;`p|ϻ?}{@ 61hdn %O+g#n7eh%;v|IvjQ\{aur\I^'.Yy%߯|$Z3gYo["oν%/}cn3+>˱0`kgok%H`Z{Eu/EʉK.}o\=}np_3K=*MQw=5G""oμv Y\/>{/:ڒҐxefZBoLWIF6IјZT*w3̭\Tʞ6,Z)f5HDzf84KUu]w3Ok߻v;˲|3G;yM7xIR kvďNM)k- "gmE"ʳlltx!8@AÈN<&@]3z/ͬ-/;ADF9o= F~}SW)쉾+sF:=Zi(<ε!AMЄ.RAK]|cO>8~ޝ:/@1mN9PnU @Q+:.?ul]x9@'YT nnS.'9oʟ\@Y /FJMe\ KADeodVPE/}yfm4{K€m P9*{!Ȳ^0֡Z< /cDdQhFF;RjuxzѣcCÂQ0(UZvV:n9MZۄߒy׮Z'>%4.&%"   ?xoٶ3 VGbzIFA Si H1lTVDS Pݮ fyvdd… 922?7fj ov붞^Ơ޻ƅ<5GN<9t&I⹵[_9zO>jw=~c?gΝ; Ƕizf~LE< Ebht~/'taZ$$`ϖ"{y>SSӱoZ҂OsҎ@ČO0(@cLZiE Q{N}TY\e$BtqIM.=z ک-"(K%| uO.ǧ  4I] A`}]fD%޽yj^cH@5jeۘ&l4*'r9CJ'SH3QlYV[iY.>̡#w=v@Kf3%Y@*gsQ "Cމ^lx<\__`al.޳0l*!wrշ$W2W "m[k\mwܾG:H#:KM%>Uy=RJv%^WQd7=??tǟ:3{XAXTX)|v=zrD)VT~Z, iaf<#Jcҿ3] !T fxP:El߽Wt.qA"or)[,stE+*(eEj~7~}zb9* B< 9x~FM (%Fͯj9ڜ'@~Gz·5 _iw倾tVNWțdz.FfBy XPrZ=p ޾ǟzoca$'R̞@ Є64Bb%WTf@hP6;NA̙C]v` >JcH Z 5BAksHH؟,jE,̥GN&1G5PQGnwf= z"'B`th;0QB8saԹ^ %Y" I}aOc*{?xx|pp# Y/ ˁ lʲ(ҙ'[}ڞ؂vҞ_[:5t8XHD2l nf~}ef|x9ݫNc)Y{W;!~|d`&I. mfb~^wOIp/\BX}EW~}M5z{!0"o{GGWvDsV.گҭmP)$a~ ,#2i{80Ԋdbr/?s?wW27c)1*vܶmvX=qjq`Z tњ6Er7_Q !epy)˷H~1w>І.,\XX>d[yIЃnQB,%]z~ttޱ;:#{TppʥfGf(pE$&KHi,HHN[Y{fa{rbRP;k yd4@ "2}AIAZȕ8uuZJK~?Z`:س@V aM45Qx#{+t}9>h8u:}_ʽ_Ӈh\f΀o?;OXT/>+Qzyvͻ wsuNy(#DPQN:_}w`m DR"upޫ=" T`3spz2|fcTFU#?X{YF@1i5W\Y7 Gp튧BXm㞥ﹰ|?(]fƿ^]d0|^͈H) `)I Ը`'DڠK~ oERዟZ2qcY@QSQz BDL$pdN@hl*\:+>[9r"Z=&z'^DTAH)| ;v*j%mRF~ooAh="",AD 3 :r\i7q,,tK={iڌLL WKV5 7䡾:@"@IzN={l*)6lhH|wBMBb^E+v {oEPҊʥxo9t{o}c6zae6 iR"b$YNE#R $Da"!3g$Ry 8aEʰxT@v:H|,͵?0`U* <d"nBѰʾJ KZ{K 'O\jM%x/P!ry3nOxYUЉlޑŔ8"000Sz>9O{ݤ`jtzcC`i +D{PRv&\sıo?;V"% XaXTvяL.0jnYbˊ( KIASubdxƙ AQ@Lh|yf1O`NfY\ "$`ҚIBCH$F" #-(P~pp>=yx՞P`trd)i֙s = pu9;JS9>ۑ˰F 3 /Nz0[@ H4E^6^meE!DO{YE_cz}Mn4uzY(y]7)vmLTdQm֛ܼ'YHP g?wm1xX^؜DE.C"{w;+ongJaj-}z#w r`~UBv i ]o!'B(s $u iMH}bR }G'և&v]136Aw$K/؈"b/Ly^"3s߹ {Y{ıT8zPX<`)@UC"HEQzqO+*YF X[[^3lAъy0]bBxo5/P1/Y^j[ v}huba/wb;W=9-c/}!мR)0ya E[:ﱩSF/|DًREgyoCj;`Շ:Q(@FHjY+Q Fwa)T|aֈ X^FmtzVP/ Ȗ? B",V;(jckF&8۫M]!OcS 6kPeo|gүږ;S e$ 2"EsJic !{]*Gz>G9J'F\[_mOtJ<:AAwEd^g>z;tع}kvt'mٱe`^g+>(p}[\G,=!q9wCһ4{ce @E\`Vݶe*0l2pg3=Na%D@`=CN&0R[ RIl EAξU:ɁcND49; >䉣@F11l:l*2(G坓zqr/AM&Hf}~K& !;^)(,xe+"H|>yZ7&WAյmQ!"5Ru؋w><8>Xk@>s>$t8BpJx'DFgeT^0[S+: kq3cd=dβA>\y&lS‡ ( =-.dM-XQEBЋrmpν+e\󙪩4Zq_?!W/)N ΠBB.$}ݵ9F+A{v F5ElERdNOOؾg**dq UMA J6L+kw*uj6άc|G&ӅyC]D\ ;P ͠/˕飰sAPe# CC󏝶3-h[hJ$o,{iފKCҌjN+TjO`Lu]krm惟|_eN\yn[OCH0To@GJ@Kz9۵,wЂXk:xz[gCKU;PZ3 yI3\\z̋2h҅|>\dIB/fw^AZr(Њ6>O$+<ۊ  vZkgOUjtrj/ʥ6 4\(zBZs'UBcw?l5Ds;[m8"" CDNΞ>up-FDP}OMm -vl >?"JiYZ#Ư`}yiޱլ˓R ) Phpd˭`ё\@"# 8a\R\D,fDRH#!BE,JhuT #k⬷gxlbXɄЖѱދ),+):"pyV+Mw';vУ.0;_|,DuT !;b#t= - K[wougJcESsji_8d!3ZWO"TTH5 1c&N.A d$d *vl)51@k[v?}R I+TTv#EzAQ6"͉69!a4M0gC>kf mDڝ!bJ6*Wwj$0DF:dރV @A-*⅁7yAX=0 0Rr p30al]Zj̞G8y˚DCXHz=?ieO}r"o".AT}Oyq-@5Ƹ>|cw``_dbzx{'k-x=0߃^/_??%"+ (33=nw; A$1Q13)E,`=tR][z3+ggyLT24#U Ndj׎[TCFJ`=sJ&jqD{wcFP>V[ Gj|Q` i s(@hs FClnE2H p1Պ3s|/!23VĥB`H4.Ÿ5jgaR InE&j;6>СÁp>D>}̉qxݤrmeִ_|Ql$O#^!}u*"\BlzJ"?MoĮoޑcŒk+Ƙ (X.KJ~? H+Bs'Ҝ$+#kiC m%w"g=JH A`Z3^Zyr]..WYڅ@b$7V3ecQPP@H:L?{d`dܑ3ЊgNC u=cM9}7 W\kU祴_o /r%R0WX@7 '}ٟ.#+K}紑8e=VDkA.LP\Ris9O+KgfΟ@0T};HE6̣#w*.͉;ZyZߴڄؐ̑˭ SC"7"H ’PVaS3aaNv, .jԹ @|{w-РB<)R9{0&O>,OkBϞsoq/0?W{xsqɣk=<[k(layv[_}d[+s"%|g/QZ"(D֖rؑ~g ;M-^}|hp`QYAM zG`1ԥ !8^) J ]9vkNM=`shb[\𮻲;|O\AH ՞]Á:>цr.9;n.KvO~Ҫwj5k7wjy2o @I;&$*TO;z>iedQ{PՁ>+nХ#/a{kpfϝ;}YBՉ:D+Ij]JZ}brdǮVej eyF^b]3lpfOYUmȖ-mҙ @| ΕB{qw˫yͻ&'>y:42Z e6$d=3~᧢>s!뫌a[أ EF֟:z_5c-w9T޾mR? {hP^_ r 3]0{wF;8J0(2AsHgtNRrT2Vd^wq8Yҕkw$NLSV1(/"it./\bv މwIISX حۧ6:<͂LLN(Mܩ&p ͮ"m#ZvKQHrRB=w޿e啦hXRD^hey{bT+WVzⱵ=rT!VbT/oْ dje:ܾKŠG~6ae,^B1yG؋|PLBMh^n'gg+{:F|KK˪Z۳o'Ɂ$ ʞZgrx*܌}\Dw :9~X>uٕc+X'zųQzSg>O~?βV .l_",;̤""d&,q_44`ms{H38X7 ApLZS۶a_x?3ǏxIˋjgQYjYV1 ա{Jv ûvUov:T)ɖ)ZQ=B>ܚZNFjn;N\.ZZ° vϴC/kGA.3:dܗuTkJ+#MfѶcI;gQQ䃔c ]rX1i5]caن;^LR =c;oaێkDž_@vo7He-H$.n! "q8iAHD:֥y5ReuJG n㋿xYIEEZnJoE]׮k e4:  \'rJEZ=ʨwLl5u!QDЁu#hO>oGC?{lX#\rw9"ow)"Ri* )\[?#3g/s. 48GPj c n&ne%:4ieEe s@@BAq'=෾t^)2jxx,c<90jTr8Reϗ\!$Hx*"9{5 *d2ΪN\gg:+>KJ^@/YTF+u3Ks3թx|qant? }9B$=>o™__zpc'ZiM֕DgO?s_ kMW{_s$t#$6Zu `/IDmց\p,u|{O՟Z'D%HgæEWcy@0mG@d4|Ȟ+MNԂv-LNN+S$u@EO.C,!u9n.4yױK-$>ICR>cfr b=kBہ`w^gK=oŝ#A[v 3vnqA!eFƫ8 mn*]%d]/; 6m61]2»)9k3 l@ 1>~EdP%" GBHtq~){dTLJ @63iB坷iZgi u&>3K@Qcy9֤=ez™ *|`%2V:aPJa@L4 K#SV _ٺh/%EMb]r.Bsϟ=y<]$5 DݕPPHm.u=G?fWᇋ\%w1+^.U۳|-]pk+k5ZXD)]T}mmjs娢!EaW( uJ; K+r_>CY|(-9qhƳ;~lj?񃣓c=J;1F#ALԌ9~dls i6Sf1"f_ۘw=[&d4HN\꫍oʣUtLH^$Nβ qÕr.଀*׽NRǫc/EjL>А(LpR=t؋֊<姗sjb'_jAf{ #,tӬ3Bvlfji0,a,htY0PoϜ5}婙s[G~{+_ˋ-`ΥnRvH _v^Ekz9k׵@ Pi(}ϟ7{x][iGD!{[),:Zd=.D Z)l$n/.\tR)aAJ}}JAZv#kX@9`q]J /x:5'E6'\E΀,❳zUߚ tON/(4Oܴ{l:>sa"@7.s@ryyDת…Ï=:' Lq %_]l_>ʁ$^*&ڌ8ƪu(,Fc`h ou9>(ݶCOI878KFv9t;@J?uTwk뮽q'wFo;z赙VÊϭ/^xf1QrJ4Zq ޕR}h\;33 +)T0>Ve-EZJ}aReP !XhtViE>Ӌ"hCV`˲T*]l&Loa5Yj@D,K,`T`qS9Lՙsk!ted#S|U{띀/+mf &ȿ/ھN޻7)o5MR5M8۵2֛UW _Mp_>U8ـP3wH&TJnQ?t3)3'͉7tXx!| 4?c< ̫F+9:l,68A<_u|y>+ƊV@Dț=3Zj"f! E(ֹ0D'`ڽnQDe61E'! xD$MdJN94[7YvҊV"$NٱwRχKR-r(Ddcn1s?:^,BgjmTj#0m_E AJ۷Mp\m(>d\pY*Ă/8[ny90Z \4qD0ZJRP*4ͲyKDYyV)&&'o V.,.X<1ga\/-7tM7(M9 M^|5%]_y7n`['VJPfq'6:0AZ H|C7xʺ;{]ؘ/H`)vrzIS'493i@9ՈMebJ8um5n/1qwLFwNlC8yĞڵ}wY:]#Q(oc.;Y|dBJRMr)[Ԛfgr+AYsbV@XH;.WgBh@CKRkiTeIW`b^F_MO*",ؐ `.p6A h m64P6;XiBqWSgZ̑ҨWT&0]fyԧi^m޻,J;`]n;ҮV+:Nf?|g, MS7F{1hR퉮^Pu%=w.G*T T8[[={{,0F/>FImTi46zv3=, \h's 4u"Zxr$O^CvZ/n Db$6XwrtIe$ui46Wy]&F9[fE*c{Tm>HJ:Ei3TCաzF񰸄hRm6fWTg(]v;y{~m^=r2[BwP n[`urר^/ÅEZw.V‹>."$[Љ+}'O{;4D!l)TR̙%Q8V:0vA`vڭR~ӟAk8@GaDկ_ <ؓ<ЏħnT$@J 4&gO])Ο>?ez2ܘthxPDjip:X|,=Gy|,TO( 75ZO~!@'kAY=\5u0lZ%it^|.Eng NL7sU C;-y]a +aDRgPC T) *RV N$œ=65ΛƖ'_Xs\(p *j]5ICd;4}$83KЈ!(zO存ѡՠ "ȵQas%#t""iEW^2;9罃Bn˫~ߑ~%W+Di ށ"y% !"#*Tչ [&mݚNŒ`Qgs# 0;v켰mkcqƊGEjez ]2#  6  52wn @)D"`D̞s"Z3r~3fC!puRB];Kfjk҄&&#UbKeQR7}WڪaӅq&&%R s)fj%(8M{ql²ca!TF$$T zUôk$_m$VjQ';3Oum PapPѐ0 j+ Kr+ae9 q ]7i MXkBN!XaIG#N/o0QL; VЗ vɽ\i zBA`Զ#[^Uq: a&1rɣn =aMƩXGB;iv07gBe8F~q:=hd!8^#.TCǂyP?C6͖sYk"B2F ZDz" <YG1Ƙc~>h_^l rq-RHFRJA ;og>+hLۜ'tgIW*1W=7-eLun"gxر0,ς|m]x̳OD' uU' ="0|>D{Wn\J[\ #Ez5Tx;w}_;i 3hU*զ=h>PuAfF\oXW\? L; 66c|lZ++]X׷n۱l-,/7;=Pyk#A' `hZ$@Sr$itKT'ͥչ\uchZOJiCCo6xvviu"Qy"nQ\ €٣`gԭ7?%k%dq{ow> U|$b+#+~mg??C=4&_~nys2@ag:24nu$hP;9X3˳';T ԃh| rcYw}jZ\_ ȵ|C_FRTY pۯW XI]2X9RPųKjAPKi30@Dze*w*Mh lc(sؘ EvSٹup'>X_^Zixs{hVUYP@}-BYu9Ih7/M-@/o2K Af&itM4#7#֋K}OyjϞ9tvgq% u{- 38o={"Ճz=PFefbR(ژ4AC">@D2ڽ#}wٗy=ѡ{NǺ H9I\nV~nӏX #ɱ銊$Mhq8т"Du"GNmٶZR F g EuR k4ڲ8#b}; A),\mRrw]n*w-W{-eey([%"E$y7xwD8|Ld A,,2{;v_|_v3g+B 0R NֽIfEA.a&}eq.\QΜ9a`\QJ8DNvҿ_s-uAXoe09ع#ڵ[ QʁArRqc| Hxk  hPC*"NǥIB(a=^mRkM27k@aDp߶񩟞)\REU0a8Nk4Pmݳenͽc?vl ƱH)XhP-T*m*j-ȁ'~wA@BaoRj Z]I{XϽ~Uk5[RݬWymԿ<ґ(k :O˟;?hsJ 딃 AjqD fPޱ5Ff&&&KɕKlV~4R4eW<5I1k};WV[&6 I}+: B=2.E1 v,8ש-PEcX罋{gݾ>^8B]w~Z6{D))_zBj""82En@г XJ(p ő9wf? VKh8U,$4?P$9Z;SUIԕщfW;N=(AQ) ӃD. >8PuRjKX B$$E&0< Q䒂ixe]qu% (!?DZI'읷֖"r-n!@zN H$tNQn_X|#P} 7fI=_@^P;c"M0e@ CGB~rzJQ?2ngzdW^x ##YQ)df YYD00$M%둷0:zo{hs ` z(ڑ7y`e0܊:oLd(UWR kp=M A ' EJm-.X $PUȭy'?vwoh2}$ ޅWؗXmD= >){f%\X5nYP,6F}GXZg}@#}}>O}ldr u wΞ"> U4U0 ݳ++TL Rh%It> I?{KYeFs/]}А8.6LJ\KU*[ҕIQ( ;o|'~o%?R9a@N;STpFbP}_{Fh |DT*瘅@+XV[kόΌ63: :ܻwX,-;u:[X%P5T#42q c (rװ\JfT xZIwZJp]r}[`nз tϯ=:jTC}hHtPǵzhB„FEu~'B;1Q6n,A܀wR :74C@EmZw6qf]X>>3/ )QVّԫE=r\deف\RD^teu P|V#"šzzA$I]dXPd٠N'[k덑ߵg?l8<ꕳ4dPQag;Nou[ⱉJy ,ށܵښ^鴲"4{ɧ?QA9,.ыxa҄:qWsv>6Had֊qEQt:MJO$4 X$I3J>;ޏom;I& s7+JY2v 6x~v{ǝwwuVg/\عifrL;Ohu7߹uƧL.--'vi @Bm] (BM]`{6=` <j ǠzϟnЙzRFDfA@+,"֚6BRp!"h}wdeX^s$8qV7@]S._NW Ჭf' 11x*h% +*0Fo(MNQ{&\?\XPۢ V,lH,w <Ո{/گ-Vw~wjA:Fc̴j51&#pT~/kJP)z޺E2Y+OO^^ƹ4\"9OH9)a鶻jB!$ Uܾ}9 @:jѩ91B}Y/6+ޱkf̠v@ZXe+KpR@6+qH>އL޷oǚSp 6T=AT٥F#لF^ 48'R~}@7}[vE J (kE gM|~wqQ[)Ji\lSNqpm{ G0yxZrgΝ+xG>o~n=lݲ+9^ j\R$ g3ֹ GTx/|'WQpIEqhCp+k '< $?O +@j-G۳=l,J@lH@v]AR ~=3{TweFA p42 z( T`L--Zt蜙6` b2>jm|'F8QDx}`FBB. > J1.  4 AUp ZyE9+agJ_LDd~,/,.ڱT1hjM:kHR4Gde}_)&P7 ={Vg:N?71u{ @ĸZ+5WXGAPU g{wn7-̉;w(e+Tj l(Pa g/@&Fp(h%A){;zpzc|1˲Jji6H֖GFad'H6u("YޏL ں}+rF J33x{Z fn2l-)"ɓE=Q<\iq"t(#Ay`G_z)jijf2JXAqK&mq3^jUH+PbU%VWկ[]B=p#Z'FR ut(IZZմ"qi(vέ[i|jrfz:wVY8'L@e1}yQ`/d!T\'r^(A]Hm=o=R>swms\gŠWҬU*p7VV .ҬJxWXbzV:c "#Qm^`}r~C'<8":*04gBk~& hD sO9/jZMԠ^kJjfFgOlH-*H$E@# WзZ[λe=s("κ <flt~̲~km-0 ~rkHԟ lJ< bѢ, ̃?z=~36EehL@J5s~YϋTw+HlD) O6#Q4@!{'~ H c[dqTD! `DqO&pH "$liߞ;|/-/(ZNsЍ*40p]}`m;"bp/[^t\ (ch_|h] y+ N룣={[+k"7얗Ww7NE=E!h~ϯuAz^shTjVW֦7mu޷[1{enkwN?G~f6TCd-A\ʍnnSwϿM0v> H+c Gj݁dhܹO8=ws<ŋ*(,OfB4 Jc猸s^ GY.$sKuVOlV{y) dǮ]SӕȮBafq|ƶ:ٳFޛX`"-/xjAFGq6"( -s)sPXW>OϼAf7PoTտM[[7tGAkMHY%iӦ~v:]RPLV0Ertw/KB= ̌78FzRL聡TR )WUD.8?VB`JCLa60c. ?+?s϶z=&g D 7^ " Io?igsP4ĵCjER$kw:}{oۣ nlK4;jx}e=jaaPN]0q<:QA__*\Xqg}>ZOL|H7?_ hJTM?w?xo~ 0}>]lۿ#U /Iy/Ͼx$J 7[^(@ &*۶ofn*J֧w': kD[>< z FkPo(Ш@ѳB"/9̷V9Q2 T@d'ݤ!4f Lwm?u1OM}~(׌h,fΡ!bVB *)"FrQA!ew' t8'G_8|pӣ{掜]2#.[v&=cjCoXҁ.{xM<n* -DއªrK0*?ιU y QeC(*gJKX HY]~ŃJ§ BgԴc9x{*$qJtܮ}wclJmzz\6Tuv`p#0(4Օ$]ݹg1& <ϴV ?9 >q =?KL `1ʤ6o6&v=*Z kVk u. mAi(;t&A{(2A zyy33=< @۸B"D<4]/>ҡQ8t-/^{q=[B=/`{`{v0CEB@J{UKjuӔ(Y%~lOwVxJykIK$J+EJ62hI(ˆBgjaP_XY\^`yeljb,;z0tsK(d(:q6A_aTE."ˆ\e亊[*NZb|>Z\pI9M&I{=upmv'>H^k/,E?xzYP@"ba!!"kP%!̌Yy*ү;rhrD rI@@5QEc)Zk.>+c|nN-Aѓ!T\z&l ]&c@DM#S{7iS;a ]Jik{6ݳџ{aeԖ?z[-!"(Ej<=5vLTj# "ςXkhvioZ1J¶x[QDT&.<@GmW ;B^ 0 aR/'`R)ŢtUXa(;vnmwVFD `6!쁓: -H0( wޓTkG8W2of6~_#UM 6/Ϸgϴ z#2%J%PN@/;QfTYQwlPS$7p]+g@X#*ؙyd5 `AF[gjJaiKUg">쾻njX\J^aZ>I/ iqո.Z11=cx3|S hp < _~ݴc&uW [Eozy6z@\& C/OOW[ykә~vj뎙kyݕ0+SEX1{0 ),b B@Ej`d ;q .C*FS;Z ¼PhB&RxYpO)s#9PX=.dB@1A`$s98g&fBP1y[Vvo;3Tm[f$b>\ǁ8TbCEA x b,)*$O/ P4ڽgz[8 yYF5k kHUgWs~*EL<э q{wwJ5oL6PX!희o+trP7KkTc^A@ "=?7~?WAՍw/Z seh6%`Q td$P(6wKY"@f+nwR۽N7A-¹?O rkKI[z]TgEd)g" rsq\ }[:Oܷ:5t )CΎ,f g R hGz[}/|k@bF[:;sRO&"m\Z@߁cp9X˜{*^<+3VSl{5]=pX'o韯^踻HW*FO?ctO*a#,V2_˕ژm:?ʟm!]h@~oc]H'O,@3=᷍r'> Br9+y4M z0Dg}(PN-#Չ{ZITbcmʄRWR9,/w/O 7CT`",>;ձ**zT0~Jrr2)!6j;nK[^;mݹVu _ibb< #.zbβg&'̋?B xP-OOٹ'A7Ƌ"'(0#hEys0,Jam[w2a Ui^@ $`QgCp08q(u,/6+vs'$Z :{ .MQPXǨjB5"z !τ@OZgAvG2|- pOw* KEV@ie3ӦYmL4c>B([*ES>z~ϡCG wYm3s/,:&WV>"5X.:'/,kEcb.$+w聧^عsHs9zks_ȣ"'OwWܬ,"^Qj:4hoH _T4ιgN7#e+lN2EaE$˳m `=j=tX\oO͜ܜI`T.޳!D2ffƺ,n߱}ǎ @ UKXKMp;vA M A=R8*YR8q6BzO^lS ȍ@(T_Z{ j⊀X ,b^2FMf7pZ@ˆP0ki6=s-.?}k*s'fGNm@ΐd=`XqQk\ dwo)UNabl-&ZO6*ؒ{rwh2^M7ѽ| R PZyu^隌ҵB^%|o$f=V[f"dE*B TƤ2;n>uID)&:]Z^ɝ t(21ژ?y^{e j뾽[1SɹVd=(`i +KЗM5p+ h4:ZiB r<1#g fGa=eka2RmM:3+StF5.qKMq\r.+<5qRa;aO݆֬n zsk$/,2Lu"Oc ob xy^),%~Q!W4u6I֤uzH/߽x ]ͬ_0ͯf jK4-r|v8K~ɸTޑ"@)Uˊ=[l RèP,b "pdj `^F*CS };9p R fc((K8*B$tey@<@E"uN{/=pB]fyZz^$hDkn6Z#6 (ܨ" bg~n^D3{o _V)?LzosCs5*ittС8KCZEa&>}ѿ?C*q"+mUsێ;~֮gQ3-an]9{Ν۷߾k 09U`kAQTjPdɟw[O> *4F|[}~jbfzˏŮ^H4RpT/O=́~mRv6:0di\O j]wwågjh@ܷPV,\ eEBR\JH~GWW۝^{"*8ZDK x79FM66}xpяP;EN TTe߾1>rlg6d_9X%a.^ :tPv_nkP}HǾ3'N=R̲Yըr9wL\-..iZofEMLMcɲ}ըj$Ixέ~(>z"H<=:ҬK-@ yuMe\T Uԁ>#2+Zl0IH) XnDCȒxٝY>l(|^F+)K,ly(9c)VWz U*Ȳ\ g۶ٿR8e `o(,Uy2=>pus{;Nc[  Zw9=yDgpn=ev #іMvz8WkXT5wTksi*XqR h$6 ZjA{&lS ʍ~ ytc5@TZ~uY/>w>(JQyW갔!SՓ;XIp L19C/,yFӢ-w* 2~uj@)YY]-rG^vpq9ݹG6HJ4`_.y{ԏˬ JR:uvjnL,/_/w?x`YU+U/,eZ)cHs dA,P^/fWVQ}6>&;yw߷ҾMrDz`-Hn9`06@j*y,kgZƚqa\ \vID"ϗ'8n7Mʥ5ebo֛'nxc|:!4+ 7 kbΊ Ja0F D?p'Ϭg 3t~aaOFklEet/Tu<{xㅧ= 0jR"C @SA0w.Hc9QcdY I(cV#ys" onwdWO8{Air~lk)t AR,3t)qIK%0Bw޻GFc O%SU fg}\9Eۧ Ł1ZA͡AcZ9Z<(5'~#>%-bɅjϯ-ùnn/ G)G`` U}l$47O۶ܵ(vݻ}@ke_rZEF i 2 Hpً(⇠zFui dP2Hdq}8b魽Akkh`s1θ7~d@ KS&|_UvMIABm@VdB!ү|?_ BCq@qo}ǽwoyg(28Qlvɐ@)hÒŇ]a3.|D8BM<_=ߐx*NP_ҕՔv=`hU8W hg@{x=u68$X]Xf, |lbRq X 1h' *jm XOmyiH%MM#FXbJ vsٙ#[}  Aze79 "K:1@ ׁȎCjmf͓Ks Rsx8o 7s+E]={w/bck\"6Gpaw+QkGՑX[|M 9tw?<ۋZ!C [9}Ij:^_?HH#ۚ|?㨟&KRybm3` (1 rQ]sPRIn; 0 =3Vhċx/ @d࠹J!epv@xA!Fye==r5q Ο"`;L\n5 KrKWPZ7#p}/PaĶQuDz$dIQ\wI;K{o|Бcf>G$4/$RyR9m4PJV,L餽>܌~SQI9`^x'᦭ov7)G1ޜJ@c*Q J8V$hu|M;ªE|N#]lRVxNZfy KFψHJ " Igywrzb?sۼ}nݴyfO9&IYGq/E\=s.t'>Ch9@YɅvo=O+}s4xȠ:v/zIzt]-}B/]@36vnl;400SyA}PQזO-kjl!Ͼݧy*奃o9|xyrۮ.Dy`Eh !{`'DFZl<}r# 0 jX={ C9tP)$8(xKāOE낀 sp9Ĭf7ԇX(%>.HCr؆*JIwZv9:e7 o_mrUxR@nsS@7o,2:K7a[3u~W Dw(fλ]ijm}n 4QRLmJ\K*xk5b(;"I ̈́d`p板W~es sVyY2>>6 ή,ۢݽwh l̊܉3cwDP0شC&U@ c9N9f.8DA}}_P_QZQe{.oL$RdE\6wΝN0##cTQ6J=Zw߮"S'fðgr̲p FQXRYl͛wQ 7Kㆤ P!!*^LTaE^듿_:nV;pN [(R$`y (a$ ""o2t>|;N9u?WV zC7з ˳f@P@*,u]AFg`+^}~z|9?m:,꿜Eh@1*Ju51v ɠ1rG",80hB@fI@Qx`^(`(rdJ΀msO/Bx>qqfOk[֡QDhm|_hpٔ/dmw <}ήA 1B $0t! F6,L˷ P.TY]AP6L]F>-Fk !zILu ^VeŲ ""6"7j: 2oNq\o Dسs^0 iH~p~Koi)^0c۶UcLIFSGE0[k Є਷.BQx`Nm۾{lbE(X6jwVhUt96Q%-3)_?V[V$ԡYOJlLX\Tz_TK fčW 'ɉ'Vf_Sd !(ԍzeLGvbמEu)txw]jVuVuR<+OAlݺV&L|]L[2+GX y>{~(Ϟ 6AdHrk69JaO9{ +IR( ˡ(P7#7mwˇ. xf0‚Z#^yXOγNգ ƣ .f>9!cM7Dj^<Z$PΡ  I0H.K l(8:A`5pZb(Y9PhgSG΃/h\P9g8k9"#eT(Ƚ_KS K9d4,K~ _]*;Jh~ő2k/5@ecK>Z%kVZ=R]7<%_JHe*{xgz+": = 2Μ4j8cӮ{(5$z)[)LstLL?> ɉ1Tvb wچw~R"w(cQin]i덑w=xiLy650{{?3GN&g;.:BRI`$һ'u{Se gAwd{z 3R4뵢{xw<,!0u;)\@%q! \q2!vN@Z!e\z жѹ^/Ba *-3J];Χ[ QQpocub p DPSM{?HvfW:Zp28" &ApVPy4!yf*y ~DD!¥ G._>V?QE} k{NyZߞ408@JC*x_GIֿ1f׾-] t 8 \iZAVy0{@RLzHC *vmݲcvҁ ‘)eyT銠/^ 6CryQdvuenwȨU0||wx!ѹ@2ZB}OoKnv;KmRj9O;{wmo: .P\࠻",%۶^Y8 `Z-O(I6Ğ6>}Q\H0{9t^4옫 K1:0Ȅp?]ɡeQؼ[97#~6i.!T2D= G1wM7Qg=o3|_}y(1s|hˮl.%As[TVXL, ~8>I2lDQS|jfǾ=MY][{4o}+w{}'N۶m?~@zˋWֱw6m(UV,w]E, e1iƛo|^A,d)( '}{*l ~|B_Z`AФ++ ):2A`_쯨3A#,sR t}{/Zq Qg{93BA?l׵MT, ";8j8p;Qf p/ sn3 sR@pȏ옾/<&*#60K- {K҅lRvR^0tyJ X PX7Pk) y >tl* ygI%uˏ q 0xVpp}e;dʀy FB^KǕ97j"8vwzrZQq%Cž"nz&t%=ٿ9 $z5) Rz;~mo4JhOm q~^^_{ )\;Ms "D:Hi>Ϫ n+fU}^.nH~gg~;a~Vd G0xO8Hhdhґiֶl;aFPgg>; (qd׵k,'wy=kyxv|^oݻv#"rld i*% ueA,MJ w3>zۙy79=ID)"SR_+uü"yO t;6׶\&AA4BWޓkLյ 4IJbT\/銢-Z-ZҡR*K|/7b'zWeDD oBZ<bA,c)hZŭ{}o}AQzHBOAy$3k˽D:rrpu5YI_@EOm޵aV.Y eb SK33TY``GngEwc ~ Ǟ:u..Ϳx(UTXmݻsbƹٴ߫n Fn?9R{E!z'sEPxEsL \*+JticIJ= rHF5p0rE)P&xWͰk|fR/H)WNh!#8봞ILT<`Jm)'$$j74}ᥛ4 'ñ]:MtlxJDBʯsx@)4at-Tb++˳ uM_azH}\7gxxة#_Rzvpّ sgf?WoN~4QZt.1 ʄf.cWr"cweR„(YoaT(,K%Ȟ1)iEQAA7A 4()JV5ŀ lG*P]z@ݻ'(JDkK+8 cJʲgY4H^^$D@\>MnU:u3rEڈV>>>ճE}^FO!e` mV,W AfݮV(!r/%B qj}Q\X /=<@'@ja/y7I҅l[%? !/Zp=ǖ7ݻCW9|pwl{m{7p)gZo;d1`4֐M1^ɳ,zXuqn40j6ϋq{2|Š/p7BV/T@ Ոa$WjeUUπe~#]T)| wv0vX5uC]ǰ#Dq4vl̺Ʌg1\29ᔾlCFHtYkф%:u[Q<*ĥWr򲶙\ 纙!{aoʳ,ϋ 8Z^x @E##zɩ)kWU~,+ EJc]65Q5Bdք*Tc/9}ڐJ>("dfy;Wzm|zfiuMYPcMc#c Dx7ikϾ3 hH-^_2'>(uPk-a,..̞;cZ.MH'yhgq]0{][BqXu,/W.s)f&|{?1sO<3RJY r?Ax1[vZ)!m2+ Mci6G;>(VVuZϊ>3g H 륜 ā+ CbT 0Q؇.0Xu*u@到Dj}q8}ﺷcO,V).RkA~=Poz>bZO,EY/~f&"av>R Py"_Xc_ϝ=?2>=z,,&'ϗW gz޻_W#YʂN5LQrh9vg?ߜK!/&yRxf$|b:YPνG+Daոb7)o URqL\^¸V8@ 2k??/]lsH[g,F,@㕲uHhHq&B]fW*.VU-lLΜ@§Fދ2}q=+|sll鑱 DKU =zX ~O=^? @zΝ1Q畀BMc%-_kBKZdx!iQ#1{":,A <3RiNo1:@9i": >=W=Y#tl4ެ=rrbb^$VkU~je*Ibsݷ=rȦgo'w{RDB r@ifYH$;g*yQzT( +aPe=b\ XrVV*Q., ؿ != +rP`BtROt "`|zrݷ{fZGXhfsmw_<F=P@a-*M4yc~4%{: ٧XI;$_; ]-}Ͽ2>5v%ugO^ YM޳gm[=~?/`mxC)?܁ȹd.>=FnPؒrE 0#ZI?1&j::("R"?Vn—νU8P¥g&D|/QQ]P'}:4^8sʦum+21R APxt1>Tu,w֗*DHolb(d0K˳K=lq,ΐ.l!@@MxĩMSS}Eh"PZ/@R%C +Yhg=|xW LUUrei (LB/).\ d z[lƵ#GɟCzD|t0MyE<uR8~/ v9ÎAP Ta26^ *-WF;63ӛm B\2x<}U/2a$=D| KrKMVͨUZ1Qi~HbAηA`#`8tWן; l!Ko~RW@ugfOml=yLm|>5Z0&mv}]V[Xv$ xvo^=2;WHAȑ$nX7PTآ]AM}yC~+r$J[:S Keޜxw ? )VkE={'raA\k,rJقBm8UEQQ9?M?/~uBA(`T&3$Tɲ4tZ1AebhFQl||;uy P^#B" o3Y{Zj@#rBd;CG.,HepI X\i=qcXL n`ҥJ:7Q%8r]uzo|_|?GF#qO>u?;zݹCҋͳ,-\X}Yo| "T:/\V/ad-JG؂=qwXD¥1 #0l,KJ (L܂H)ʼn QU⥮=3,_f6oL4<~`ᥓiZi3]NMA ꕊ+8h(q(<+D@y(% 9Kr˥@+D犵5jvk=#*u{j\*@`2@JyפC  Y\P\c(@(h4YE"Ņ KC`nUiӖ^: >Go21r6;91ǟgH,N! zDF@d^;yt)Fd~a<|Xe25fc Hh6@ɷaU%?^b"@l2>j<& @×e<d0h=br~x/</BpN So?CrjYkQ{?Pȹj e`+RQ;-ed{K 0cBZrZp3~:ě@`PQLg5Ƨ W_v PJ/< BRYkb4.i0=_ ͌9B456u=?<+;,$0jsfg.}#No=X*xBe"Wp/_JkT$ `&ȃ% AyD+ t{/9+0^9dc̆Vj=&!"HurA:k~uaDUBy؃W@nJ-*HeŅťauHХnyne|h+솚_qaɵ Mz#C1GDt~l6GIth9zwu,5S߱}oj^k04(D!(Xbx3ʐĢ%g)!n 5wsrU8E7Kxyk]klf&Na{d/ s B"CݡKF-aJym}1D6.a? aA@!Ge#<0+P֬DxޡpN@ yqYbuH"`t}-).BW|dCKA_T^i^eԤp IHͶe&a#Jq`KDn^BU(רooV꿖UaV' t<q{y:ln|նnVj @+-CazQ ~?+q}UTsCX$ru>uZ1(6&(L1k~7qXH[?4^]W "Рn1UgpE)PEQu_o]glp=cF/;\@j Vɱe[zrKP Ė(AΥ8ɉMYyڵAjBب)}A y3=}>u&R`/*Y ̱0hEjgș34A*@ِ 9i{P0ʓ4《ρ p||4.\iuXL RZCx?5P 2D*znR{y0kώF3;5=Zo(B@ n#g>_zE1eCr,rW3(S2`!T܍%\k]@`E2/JP +,u5c` /%ZauMFaQR B p|lz78DjbjDGfr=Vf퓟wV"ņxOO?{ؓ x]<[QX5&pFI?+ JZK/3p^@DyO\B:7XO_rK.*]4JnX.P)! B==,hy "GųhEh7FaگX4#9ei ~m& d(E(x aq8WGQ咫$䟋u `7\l0>\yߺ "oy#60o\ʥAJ@1/|y%PaM0mKmji±";*t$ c_8G"$2Y"ųVy'#Hi:uKaH[7gV/t5@HZpbjC׳9sfvm]R0FIJKDx7aV7qq:PD)P BJ|Pـ^\Z&❷w E~wiiERk0ő*cۥɘc^[o=bE|} Rhކ 5,^Ui3RVR{ ./<)(̠R$^G0K d_ZZB&ƧaMϜ>d0k%i0>]kO9"$D 50AaeI,l_~W,i8 8y8a&HDhtxk?NUw5 $f4O+bhA3˔;D>kl6|,`r[bZNÞkX0gf`Kߺ|d;OҪ,HQU׵:y(KwU.%C@t% t=^pM<]I\dre$@h:{=Y(Y![4 Wc ԱBww$Hκ4=iI "f ҨفM-Ǎ>?09!*-uRxAy'>Աb/~NJlD(B]$J![*sЊam(\> _4{6Msh߳Mwi?׆"ԑ9F R[@ ҉oB 0H&NBAR+w=_~Hŝ@?Z "H&ܹo߾G~ke VaHӻr~/!UkG Yfi6!~btAoen{&iha\TeB=zWh&gϞ:uJ!RYIĹNk9ap Z)Oz> 5=qxi\a`"0eµb@<ɀiŬN_WKh`;FKD|?P" v斛|OEr}NR:{5Z"!qK 7sTDMmƕh Yej»2$`[H/.eE4@r9@TV8 E:EKHƩ 6lb+1 qZ.O Y*N_v驒M@*=]g%4췿m:iRCg<ի7>yDlQ) D"Աun6H],v0O Iz+4y/K-(EZ+I)+/ܚi 3" ` Ǘo!'@$ĵoN6P?_s?{Tuz۽[\X&n?V ᧟WǏs&^Zwc'IE{UPiz:6 N:~zmkdqAhnٶq'z+M+ Jk&-T9XZǦN8I|2X)˻īuV64EQD|?#&V+?EnlbRSE؜;;!:}oN=% T*ap[۽wٲaBjciRX3[]ݹXf}<[==sIAlq#_ZQۍo3.ɐ=,B2pɸtKvQf~(/x-xT 6zה..O9S- u|06-sۮP<}ޝy;o9c'h_K~r'nS!$P*aqZEW rWzþގ@Ejʏo<i#[tb?USsG DcPvxA,2qip,"rI)M2IDkQMc|4 ,TF -˾NYz,cԟaRrl~D[*D͞p@hF,(oujBD4Qڤ[U&?kBQXjrEhP^ƾzr=n6" `U}]䙺o=O9RmZshYݟ %H$ Ӣ/$i TTйiӖu6$"mzIDQT )R .ҡhZk83kyArRW=~뵨?mmJ.Ђ*ӳqP+UJ`gW_tW_U=?@eP6Y(L_9qnWkgKIbX04wv&OiZ\= bORQ̓{7>qȾ!{?Б}F^9uDcC @%4&^PڷJ#JvZaǼh/l/ 9q` ̒%3p..(:Ker]Jf`k«/!W4 2d̨"P_}6K# V֏~8eE*y   NOM^xZGzZ1<1۹jw.`ˠaٻyx kV3@M6 rнu뭝]u Vkƺs8wjȼ"X. R)lQK?t-5R]9Q/ٿ馛ؽn^qojzĉ>艑sw>|ϋ~k_ZP̅{Qi0 F#SG@޿ٯˆ' )UƬl+ 蜢ڜ֌ڃWn.v떹sNkGvM_w~2Z<btH,r q4K%8hI.,A6tuw{P*5&rGڹӇr̠XH@VN]?>7 (pgl8ǹ |4|tggql8r\ V{`ևv|U4$WueE%jZY.K`E VD޽_B!338w)]P g[:ijj7+r rM4{O x:R!W~gZDFb腝yxPuXIH ZTov̹S6mA )vw(+]&"2_pCa<;; GF-C,Z SSOgGN  ֬fVΖ@M?O޷ĸ 1;ffYZ q9,.ɪiA^|镗_zS߷m a{c=yg3e|بLVm7CՎ_( ЂZ Gzfj:( g-]AYLbǕ+:v!" ՠ`>{?c?ď]tw&OӁAy[ڔ^մ+Z.kC33I% |£3JVP7طuZ5SQh Vc[y?P~̭ݰq:=JhP;~ @$Y;TgScN-hgV݃}ze7U#S0Œub" @gDD7- Z󙺈030퇾d)ފi|c6 Z|/$yF[c~p @cfctjQ{9cܰǏoqj; %AezVΠ*@$<ϱ!&I[|^*>Bؙz>5$l[DdW܀粘L.K{"{gyP&@;o歵jŮTIorDRu>&f>RЀIeuqXC%zIHgJJ޽SMP#fAT,]YHsi⋯aa!omr96&r)hFY\ $6bPI⌃ݳw;3wŐ` J3IaMJ ^pDz}ܷ_8rT`O=*࢟|Gp'iXDZg,c"kƘ8фI|oUX',n.;g '92)Rֹ(jxCBd\Üx|bʦ9ŮUNOLgo l VBS9yeV;x `.PsP3Aovuə\_~7ŮT<9~flbjdjz#OC譻kˇ ?uٙ˽>BRo3Y@#bP 6 |G@5Fg0xQY΃9!nޑImKߵ 9)M R /{5I[mQ9Ow@&P(B |Lcr"jʱrq* 8|E|G +V`.l  "J%t;w?o8v+7X1؈x4,۔mF`*ιͿ9i!_H#xP43-3hCگuuVkcy# W4'\c ]vjUPS<׋W,q18|fĈ YEq-D̔4A@4a]') h'‚Y \ZIh Za+s:MF 9? ZfWnf҉?.#XTBSƌ8zt Ξ'nxz'3kq|uk Dlo__Jv:ծ;V嘵~o`jV l 4QqwOhH^HDF{O):7n&TB$Ih S Y+r#SGF cTrkg*ށo}s_m %>R:_J5.;HXnX$) 3ۊV)}q7 p*2wd4+2GԶT]JЧ x9BrpK|t`rA+Wv>8DAbBѨ(6A;ysGzb_#I,oBUܸMPĢ&4$$GR^8= NV^u ϝٴi@#@Q=? (!B"-1W+m dC4}خFťAG}ZunWٰ̍:|1`pB`@DH(u5Ɯ|PGA"z-zn=FM  $M-;AW+A>h!1BRD \w'>;ry[ (8]Ћꑯ ҞBb֡?AIsN…hRI뛞\QZ5|$SSS]]AZ1zZvuu)h\Zg(}? B`h<=^\.&T* B;wnӕsgR'wi֮:p6 S&GK3yy0RdjujcW)83{fjtssd,N;-$\()%ZRŰCBTĖ/VG@(^]F\x'^D=iUW}vly *$4~G`gvCɳM jO|O@Z/3Q>LАDH(2ЫFNcwwŮبZ6ET\O1h*R@bӻv%*I<72+\ٓ*(yQwP r a!x_xAH-gYX26\,}t2gj~[Ai:d\pJ!RC>քJD5R:ڨZpA(R*GQɇ~jAتF$u)DȾvnȈXm7]XPy{/_|2!C.CqC4QBL;铓!#h\3,:x>6g@%€ @aV[ ^m/M2 Y׼J`)&ۡ7E> n ;Kp?0e\6]Gחd|e|YkdvȏI{\{~Õ(S~!qikׯ~#HS )D"`F;56939מ9ro^E%Ijah8)ս݅J ;y]9I^2!&FΞY'?'zW9|MF'޶cSO:bǼ"e]e šGz_9fsb@ 5yaIIVrp5c@nI8 ƀcNA ;\JFaL nja$E;D q=rw~W>%L> }crA`QFB,8DVVA޼qHRɡ `l|k;mmER4$}OWw>6:#uV!:Fmwܱc+W~mrbK&@"YF,l<033FWZYT+0 3Zh$8 =Gm*J9+Y88$ D$jԓ4ͅRZFTD B~ sj!_(|t_Oo1Y=ٵ~CwiTkT::c!~Ww9yjI95X < [+-thoJ & +=#2=/S} !hα>R@]9Y*Oϰ8H4O(?7z褚r.P 1<7V Ű{hhMG'jP`A/IV8ocR!͔JE:Bz3cv5`"Pn;F r+g43>6@,(!0# 'iâ}?󩚫?E_MK0W?Dh3c$ mM ;@TVǍ@-ӺˀZVGM&͛lǢyMNlU`ĸHwU$"lI0;_-\HS5NdkC,f~݆[8HD6 -69;`q,(^Ux[Ye>x brD [rsu+LMN7~~OaIldOJgݺ[﹫0]*`SCuruUHH5mwo{8P FqN-o6Q4jbW@4 TbW Ű%f,h'9jA4r2ݐ<œ:խh ]=a.c4(jranft|ٴ{2};I1Osb)l hp@ݽmm?ܲiӂDsi5 )@; -7ܯl -}8JmZ'PWmbb\Rȇ9|$^tTb['+0ı8{aX-/䦧FjZ|f63~M 陙ӧN۲e3;nԣz*tuac'0ܰ&?Ϟ^)r- ]}( \X|/ו}osK80> !BW@=]:  6cչ@ }j9~pR=3` #.qt< 3@#$0lR9G;D+Y[_y|2d if8W}uI"6΅UV[}"Zb$"XPtd\y]V,'EYȩFz׫  e}x#23 r_LM4GOح(o` !]8S.iuCu2 Hje17*ήR3*'ȥ̑C# '#i׻u7r9iE )BD~SGf(V`?}3@)HU(f\dʮB'!h ,\MQ";88x۶{Y9@Zidߖ6nlܸyp`PT*ŎY6ǀ!ZNrqùӪڑR@dЁ\{^(ChI[;+֗n 6Eac~^CȨWCWIʖoݺAz:.M6@_7v 8iت4UOUm5z)ĒEBBB$vn^sgJ}=ǒ$^RLUz(}YNyÆ@)BgT.fb#OLVXuI%q3U+H @m|嘴t.ܲuNiΎ):;:9U~TV JR>wӓbcP3c#MG" u5hw_92M&k44CgXzUj0_xYؼucY?:a{յR, 8u.J"') <@PDC!qBL䑨 &)(iF8ΰ6o3<4d&RY/PIKB&?/N眸hT*FE anwսXmn?pc@6r)^KSTT.BQ 5qn>rT*@1MNNJM,IqO?uGO"} s_ަC~Q1Uw:H|g[.ҶXz7GAiT_~i :zAhCk׮=udkz6laI\vE)Ҝ}{Zb%H ٰ}CyO Ø-zS;! ) VqC l|.<&v$1jhCN*8E(g?a)*m|4d+G, HRir-_ֈ"@AqEQ zϥ%R'P@֑v-@/j8ʕ5*008g^=zy0, \7h *f4)բ4{{sQu KmbŞ:}+VdHjQ,Eeף0y-ZJIR9E05]El4\ vb3dc|Ѥ^853aڷ-8=*Cjgfrt1'SN{EG o0t[x鳧gƧ Ns}?ȣO>29TkhKE vouGq254$.J51dBHJE8@e sI8 ! 3"qK<+Y`IhD\A iZ# =Y,3aOe\vDtBs}^cC,t^Z҉ /'RFR'8  Ib ICc?9X\VB\O_G0vvdCC(q!@b=>tg~Lbԍ@ȱ?*[:$@kg^5m鈣yǾc4o{ y_8&A#O}eHTn^ۣwΪJq0fn/N|ؼfM=^/[T8;U4,A[|慈M8T[-;!=lt̙#Ǐ6 WL{H rk֬1F)?#?sU95rB1GQÜ}Lm1buTıM0=[+I.$A._kZkp֑6ΰXD"+HY&""57W>7rv5}"222r3ck F#zo`X40/d~pOhr:Pʹ]!LU.Ms 4R*&N(d*+95н3\+3Ό?Л;⺭0DiH&]w}[>;{7+[lu,F`|Bh qaTјcY5HPAREWuoAI::"d7Z6Xiϧ2/+r J>'"y$W~pR;OG\T?3S*غYZȑG_?Q.\~[2jcǎ3vݰvU>bZ&zᆫx݆ޞ\*8.ik _\.skbL跙bMXDbg1ߩIMSO&uCLS _+H, }w#3HL>sp-; Te0yʙ #jɪq "r[]*ԉ?FfNݍE8B'bu*<@ 0p_֊X&IJCGP: +W@ܐٱ]gދ/E  %YaՎY *!{ǶlPǎtzvˇz%INL+;q9D%+VHQ8+ju[|H9?0APmӳHuٸؾ8,GTgsa,lR2^9>NP!)dblڂDD1,?8<~M3'L|#S>+#9ol2/ŋD%^n.ivnJl7qҠgsǷ /:rx$Dhca HطaWgQ;^s<7y.J^z0H bGZئ6ͅ}3TqUp5zX́etζ4K% " f@֖ggY8fPw@3NR@% "B˛4%A k?3X%Z9S5K,p?Šo/"1/6QKͲ ETA0=a0g=@1̻uSd =ˏE&MS֯'~g?+R\r@8~W^bHn\r*q(+;`&mSo'h.܀ o 826ΝrmhQKƦMP1KO:^GGQyӺu==Zўޞ|[fG\g=#3ڨ燾WX$J0q7lPiQХ4SEˁ~ 뻊RE 9R )zE38P HHBc@%\Vvط XNwlS1$qf;*o/c*Djm[JNu s㜏6_^=V檍lpco䊋)7UqőFG`Y( "I<A-ztܤVSV27\2Yv_~2Q}a!RYHHAE..-fy坨- ""bPXb L_R,t3Zd.񲎍+'{R8`\>#KW6b Y g::~}?VVlf;*+]ur:D 2[TGaٽ>PX9&dQP+^f[^0FϬZ?셞'COqM+Vr#(N;H8hT׬8+bxFcv;Ӽp]Ůb iڈ#Oyl]+BT( kXdMS@rMm+jHJi\›˼޷{//*]jku}c64(e!`h 9)zD LfgpݽC+V͇ BZ>o떍H4&RZ S&Ki 2G;(LӺ1^jm J a$ٹ&)KQjj%m,GR|sPwJ?} H z=7o=~䩗2( חč(8t @@@`9AiJb9@Y(@- kVM%e{pH[|%}Z̈́)V&&1ՑHH@(HO5a']0"!NZ4UDhLHseZ,Y&!,]G$LPQ pq7RenwJ%TI`#5DRaǎ=c'#Z&Vzm|1"B()'`ʛm];s8Tc,xF>3}($m[oy'<($aE$,uuW0eY$_ 2eۖO}K'ΜH ڷt21 J{ &)[#μrzzBI70HvpN!@*+wl+cv!"y^f"! )90ڋ;q#6ݱ_yq},MƏO?@d +OĦ$x yY^g K֮bXZzDZ`aT8-F`.'UkV~$<99-Rb\JHK[1+uҗtqql~,j޻e_]vӾ}>jx,W3sbzK3:I`J)rI Q'?ɏ=}߉l;}ٟkW93EQ-X=6"@1׭߼ys" $.գ W `%V \1_)W}U$m7Ɂ mVd\ ":X jNkύ+n( b4Q#i3bBh}ݹz6j ^j `xS<6]"@ƛ7h iys5>[;wOOvI(X=Ħq{yo4jfF֮Ro;}rr5o|f6̩s2x\WW \d]-ML&csYh7^F8vl,MBr D5;[;b'Z$5cNCqZrnS'zt;_sk]BPDEȚU/mq|-afRG V v :c@>:~zd yfL׾懶O>'z"T Xٴ,{W{ul_tGƒ*@֗]*HXN*(Et-+QQ!*eCLԞ[(] BbfߣBKk%wSFΖw"ʐ̜$IǙ"pJ<c |> aaTk(.e <!07;ر;xF)l Fiavك@H}n['Nm$ I!ߗFH(ٗr= mKXD4\.7>>Sm4f} ;"@A^ߌQwGw$L L{yc ^JTh&Y9GN(J0K|΃]R-! ߾wxhSaDi_s:a i^?QCX[]WZ) Ft8 i ;H4R3|Φ~2Yv.F\>\tA>D4K\Bizή$Mg0阧g+j%]\6"ISG8 ZԹ>g *Th\\z0Jx|ɱ8.*_ϟfhaKV2xiC"F, `uh#P$ S{SiԒ&sPЫݗ֭Z5qb5egK# (_c0=p=k/1k~x y_$"u ۔do% BK)۠07!E-9y̲Ds?ߒ$Q^s}Z[46Q@ ZIKky0WЌMD&N|,0;uƞ` x: fT@t >G_:(q`|IN'}j |sڡC'\׷5h.}^'VDIBtYKߟ (Tzʘg܋{7 %q,IWi8JbG\TsǎF'ʁ]w|y|ܐKJl N ׁĉ"R Jkfeѥ/DP8#0r N8JoL I@' $fNX&bro1Q(!y134=;_^ 0]) CA5M*$i π=y݇_9F M[C`:B DBYHX4O :ZVdjQ)~V֍KO2Ix5۶܌6M=.h>Т\,GTiܳ0 \a1g|o@Jg"&e LGˉiZ9Ԫ3H T=C??893nvϽ~;4E#ÎI(e.S=vߧVj|Q JB ޒ;DZTT잗^xV fCL ΥC~1J{mWMwq-<i!9^iG> m RňH2|uf =;3ul$sa\nC/|F%/??r$&;ӟ̜ a\GgA)lٺ9ϝ>w߽%qUEr Wp1[Ħ6xFKr icX(IR )T QԨfgF_.y%\*8|䍗^:}萵B=Ɖv:zL:-`$,|KJR5bYP̼< n'M 5iM&38J}tCs/+jaHI#GIZP@47}W~?{N@E(dɼH}[6+U.9{|@ xօ|Z",nH߳*;Gd 5i33ۿ?51A@,`vvu". Sܵ^(V8?7j@Eu[@ * Υ }G>SG Сg4%ED("F2E%F@e3`=4Bb4j2TFDDz^WϪ:6sz<{عF֨zJrBGM̌ϤI=ıY9BDdE\v\63pd<YƇ-5!27BG13"e` H@)&a$0NOtݿ~<_A ™ ػr@"YGPp aag3_RLE#CB2z>:id :̠BVrAk2g-@/(X!ӼV)s|NM\#)HuK.376uP"Hǎ";jU`]@.7CAhZ-=7w?/QNBMN LVxϯ8or^%,@DNı2-$X= ˕45ep yɍֺ]KU|7_γYeyiF9ϯ z~?g~~~ UɹX$QJ!M\>$V1V*c6kd0SXtՆsu F&w|Rq_7?@c qT4iR>?Cm7*<;ɐ'o:_dKdȍ]e2rJY&[}vc_Wo}麧t#Zp ,B”oݶ-MGVݤڂ\}_0u;74D<}[FdBs޾5u6 7١`G{$P)XvzW{eG`p,Rg?kNY J^lz%(XΞ}`ݷ-iw 0ԧbۨ 1ă;WvEQ 5SSٚao&8(`mj !:i_.WkWMbP% ҾV,+ gC8rv`b-!yFkȡ &}uvddZi N5'$iZ}[5ػNݱ(_:ڋ{Kx|6[OZ "#2`),l6Ni/hl:D\J&8Jsn`9$օ*C)bEAPQiyC47"` T4MGX;~ >տ$F]Á8@qBw+|>qtW!Q1$5"P`19$':43Qۤ ! (iwxmg@Km:?9@@iƴA#P˦-kЈHBG2\o niXpLn. ~|bA#Ao @T^:FD ZX(v: FxeirLxeUIv::fgUN'fuԽ~]۔soR'iyFpq9HeEclpsxޓ{8ǨT HzH,l|UK~>R >zm394 jFp,^N,[ !"O.+,1;ByOdy Kk r%M]gggRyc[^-rn 󤴳`Ȥ㥙#gNms$YbI WRcQOp/yEg"̲GߺDN)tO}O=VZ"BS!E~P^%Z,Vʧ=Č>q7˕3&;3{O}[ZBJAbB3P[fF @D)燶oG kujsLG_ycF-iUdLmGcKJY |'٩4MyBR%"gS]A!@\߳oOԃ,'>4ǿʩK7-L R n}ž!l-2D>y0\7\6@8lO%U.Mo$k{|hڲBOhZYpnv_C,GdE<Ké jBD1BOow=Ǐ[$)_~u AgBZ92mrdfb+ @~ k .$hS<$i 4`bwϯKqRΦV)E84:!W-s3ɱғO~3leJz|eĂ"\\r2^k~o ^)4iUFKmpd1O֞ :<'oj̝z N'>sK!Z HJ@ZD^}wnï1m"9o[vu]{!&N'vؑ"H0U5mu+.B/9_Aq {iޜ7 .ElS9Hy!_~9)"!7RMcNs8>EA~_%,@ iOGlDku:.@iO}KN$% =Ri9d?$m>p{^j#ƨ|S᧟~j׮~~ȑ#33<Z7vΓ@GHD !~+Q+$ںA!JS`/|IBm&}Ҍ87δZQʆFB%*ŷq-QXp>8OZDZ_~pMWg@#HZv1F}c?1o>V십'H?~~'}\NK" R!42얻!ĵ7=oe, \+"m$ȝ$ ո~vOh`N!Of0U*p #P"shӶM#'O=AiC{?CgVi j;*UٲLK D"LD%O Q|Ƈ" DaP.@V%+x(;UKK /?Ⱥ m)p}ŶNs]{֓sg>u7Jk{{,ZE}KiJzB  (7<;;ڮc:jJTZ9kxߔe?NEzͦwן( }NJФlĶI(.|.c:ˎd*2HkG%mLX?\@PZ4%95;Oa6|މ FFnQaS_??jA I{bp"A^el*0ZӧO^jC x}> lں/|Z—RkՠvQE%6Mh!%0U?~s̾~ &cI|'>=wm.n G$mо0H]Y.aC)!F;;7++"HiF35gVm]]Fܴth)EfJ!:kR) )r؂XB(HtCĎ2s( s^HzzsoS J;qkxbzpBfq̤T',6lF% QJ֖'N{QtqS)ZPDxYq_On}wnܶs "r#ʮQ"h䇽P u= _c׾l9nD1 Eo4bRZ*a*rp!] N6v`l " I"JD2kh YZʉC*H\i ` (κ,&3O 5@"PN=W LKo~ v J`xW/tSgvrfC0),(OIlX-ω(è&18FC1{q6)8ki&,cz:wp6*I%ӔB?j4XJ2ZgHx/-d4$v4Xe9,>x's!>QU02ƜkE*LWJkBԞ)g_v a@[]%n]<ޠ 7l`-h s?B\w`pyKlɋeC,Y6V+E1A'')E@ٱm-gd+b8HPʑ'wN?6ݱ;WEE5{cI_[BuNe\#wWk?82bpEH:I4Z=Қdhmjڱc>rT{ p*0@Xu>3{e8Mf/~I>1G B\r˯ŏCN 743KUHrj rm~g>V׻|[ 7Zl$˵ZS*|>{7 i* xXz M$2(-d4b v 3;kB1fje(V9xpJΎD6;qbWV֐j=ݍZU)UH*R6M9m(Q#̕Gg&Fڻ7ɥyTS)>)b !Gw߽i*́(blb~Q0cnY1tZRZ1Y)U Ʒ9}fg{8pЦ)30.V< &I<# ȋ{{"zx8&͒5fzI ݡ)yYE Ըqh =DR G0S?!(93{l1PkZB`4uo~p˚'^:#֫B?y}xg&gZp^$$'΁UDA\!(X, Ph٥W{Y1<.sZU2qZo@I ;yZM7k$ٌ!&#R]ݻw5611FB RY fN}c7vw#T^Og2hԈ@j=:q亵n^izj VD1jE1ٺ-{Hq}@Qljyn~ߺmJbI) Dν=\0eZ#q^K: $yjg^yz+?Sg}Y9E̚ GFG*JWwGWWscը. z-T8 ᡡgh\Q'D@02o}r sJKa`gXl$tN=w~Zc?:yl aMJ]I>YT\ddCkkJ]:K.qg 0}++/YڄEz>@_<@EXQ%n4m֮[:s 'N#P+kʠ!2)cbUqYpLP)0S FR"(ktѹaieO=TON(M|x~4WL8{_UmT{ӨTKҙZH}C+˟ٸ}%KkL<@X% )4sMݽ0ԓW)$'3|/g΍w^ssQlet4 ֢&@`k RxT)$ `pʡA̬Nꣽ]G_o4G o6)H8JeLL"M8HoO]w9?qMZYhi#⥌tC?}%x514p"i?=|M<d #ۇ!*M{/|'0t%ʳ==ʿW\ *VYf׿^zD9 Bʅ48qIpl "$Œ֜Hņ"Z=|Soܿ펭۟[kb0Υ48ǰ<^y{.;|g `J 3qyVTQˌ 7޸#Hz/}[yEsI娣3#o|'_LIrLΌOxpeG; \dzM">3ǿg^tv\Ue.IoLd:6+|}lZK%J xN ;,2EI<%(rLc3s^SSjggCKA>."ڇ18ޢxG^Q875w`ׁsc?w\naT4o!lKRh$8털)z¾V9J*v+ky%*Y#荵j'LVd"MZ{)@s^ ڀ} ّg~^={>~щR:VPRy*Gos*b3)m+oy@zo=->|H%L\$feD B eEy$IB;46T 1*- 3XL sg7{f:E kVlqPoM$ľxý]=]oZkn]_vh͊_}sss}gG81Xr`E.={>v]^߿O~?LZ9~"ՙʌ+i@Xm HSc[/yw.S(/-+3(" 5O&(i4`ҿw2YOc^2k Ҧ7ZW}b4O~k:{;~\K" F'i)@;=5?ah`@0)p*նG=<~c#^}?#t z''~_3Ptbh AA Y11a9홗:#@,nP!d\ #b7LBE.C[ڱ w0!@ʺS6lg 2 IfOO›TUmҰ޺c bHԫ<13= n*,qC< X$&D7R;[z Ġ=k'5 bǑUs뺮sνW3h42H99'e e[r-ly4ffqqؒm(Rb` @\]n4@IZZKBuիw9{ngbYI~6tl|+.QLœ~y:Y<̓O䉄P-N2BeݩcVXg|qUJA,@+؁F4 B/aC9D殿|YLIB{cn.lFMO4Hw_G[Agz7wAX&&Rt}sTK6#OONTX|&DXx $Isf048fk-mȣ.!jIh 0(/'"\O,x"mX]-2y!un*&Gm-q< IȯGH0@t, $g&869^E}3+'&K!8u;C`b8?]C\c^ж*q׎<:0{xva\tRdH`7Oތ0rZ$ZQY)c&sApF&pa c]!B^|MH CO:5NYg}fmq`%uEDJST008ޝN{X%rFh q V*aӊΞYcM85mrvX? ñR m3ӓ]zTH_\@m??ݱ" {֭)r\ͧ=U<?r_{}>g{ͦXk7^5<2\ӛ(c ɯntɆOVԷ݆R":s;cD$"D|Ԏ;֖NetO^ecw:ޟ#Q}?̾Co_~5/﷮j j\ݰm~=g;$Ql*R`Fs2W,XwٕZZx`mdAH-W"IgOI:/H$X$`s:6yܬѼ" 6nN}Edbra̿ X3VR~57\K,jF6lrZ˥M2)%UL1 جLs𥱾Xbe\*Wf"0bA ,HDBLנX3HXH39vK;KYhccH*@u9}rK,|Wv m=BUfFQE3#ʣ'*\<=2I!&h:& ٘8mmlߺ>o޵>ڄBFKeS\l9''~9R$SPm߹R3~={qsϏMLlj#ŖA i,9<k=g/ @Ȅ ↜7p#NkiŁ/51[sjÚw卨_+8Sd `h:NKΙ,3 "ǮFHBL&].Sٷ]wG. DƲӫ6|mΖ&ܨ~ Oð!7L`!"b~ -$)k rLbwysMOHb|B>D'1K/0n>r=Գ[nr ﺢ%m>I|.Zƶyy1RR6B9)ZeGe(JI B*4[fr?- B[YyΊWeV~tVvςZr:#k*.^vݥW]{xBW}}7&fWur@=6BJ1Q~s/:/4xu&ql=z;4r5oQ|Z(MDH9Ʈr=S^м.FxsUT3\XD/ƕMͮ_s6i :\9:X~Ptt8k,#QGm֭_uegJM+e$L~ﹷUǟw^3fD:4W fO䀴@\Z'Cj_SS fECQ]7?=>8ҹv](l#/44 \We] ȒH,Ι JqĂF}H,Sx9tޣ;Ͻ7^iBɔrkukWRH?HR@&Rý9k֍W^5l'_Yn~<RRJφIq @ j6_|gaU!.pKx]笵RZۻr||3sNV]mȇG|>ˁJMU;/!&Fd;׵{;53vf˅cP\$I\J%JIcT1YDcIi T2 BmZ2:|zRT- -IGD?n%ڔ_3eE:ItZeB3ᴴ2Ւڸuͣ`fF>o+IyeoL $Mxw}r9v";st;ɖ-ږ9U"M"v=Wfd8aT 1sa|f|ג./!$T=Ȯ]vvݾy!EJ /ey&<`瞇~ށll.Їٽu]9&^-NNr:zOs[JT !Fh*%T e)T& hlP'S 74~*jEok٩cG~ǞQJH'hL$@r.sܞdV_xA{gKOoaYݱ!Ƃq-ua]p$IHHIJ1ƙ=c2C2R)0n/O~"5v\7;[KeZcO<=>5SOZ+8ˈ[@QH86| 3'oR]ښd} mZR^bn-лA$hCFb H *h;S WCnM0J`BC|Ʀ3fWH4k:UW\(!(7{p2a-I_3 :sv 627xژL՘AYC ﻹWcA{{~nhhhgg%۷l nG66M'}$Rzש$NY](k8)|>66NwCmY$Ŏ]%@t2;&'v@`nO|WYg]h5zdxMV-x@^~wf~;2CRDʮqTL69|h>R%  glSY%1ĄM&#$*@)I3X4iR2Iuq.jԱbG>Rs|/6vZ7rh][oz߫qAp 7+U V6l[[tu-?VCDRL;2ؾ5WGYRA (r 9&ąTaK颬5}ՉW]d*e4?@-(2G,0qMi+Jssqc\14Cmt96Da9J#ȫ, k} P kO_qրJo~"ͷ{Π7B瀐HXS-H yV,@s@& x2cd Xh2-Swon$/oU+UfB:0Ӊ1Z[Ien\DG{ƛ)nxRz{N'hݷҫvJA {}8vXRۺyC?:[ZT%Ɨ(u$z0ձ#&hN5*S˭!^Zj9Bp-w⩯<0͊8pub3Z˓(XH@@yHb Q.k.<5FUK")s]5a>lHgL]s |o`l!hz~? PXÅ|+ Q%O=w߾ѱ)rI@ISŭhQU_[#CxFwFDžޕul/և\L<)I<91-ՊUX+8EK"$~! 81;dǎP)NߟX;S,Z:Zqqm{{%QD;w e]c@k`3n0osS_Ys.cSۺMotbfȭi4;l*O=O}뮼hӛ..HľE] 0g!w(vi?xlOzEuk8$ǢnsFH!`&kv=$vV~x:,<3_d@^i9L&kq8}|h_zHZ!e }yι[m<^nO4W<11gKFŏyWGPUV #2N k4w-H88v-k=E5[rtmJ_Zt΍6mG|ȱ'y,tLl/ܼCp|Ío}o226k*Bad9zN~Y1~2/g 1cr&=? 6Ov?8T ²O;B-OZHeE2X2cumC$_Uf^].ʕ^]\36EaEHk R4!8 J dKZhkC `]:QJyRRYR~c8_Y'hĸCG@!'SƹRARuO}ۿZ{zs: 2Ƙ:#O )}uZ HD`X@R#EaOe+ֶ/B rrZJP+rL'@jXclMNך~;A&\ڥtZ{Z 8K@e]$O]SKԕXlqIַ~O?zϻIb=ORu8%g"8S *ڔ>_:_+S}06('!G4ֱ I)]5ޖG?=ozd{nLI8 K3Đ۾e捓Qo敛ġ՚hG"VyNpN8Z0}Iũ?|(fJkO}oy u6JbLRPU39cR K|@iˑt :۽cN^ CMKeN^:KE -#p 3ֶ֖x6n[޻7^#WW5YrLsi&!W_<INR` c, hDJyrmX+"ߑ@1!R]Z`l~쏣ep#ѾqDDvaiE{˦Ntw+Niqg{6D&[JVrN MM󭅮vm" #$18k]њ =p$@')/C_:W#{EBH@F )8q8Iyս9k1qVsf/Eá ?:u^Ԯs=JL[oS@+E PR8@ps&gim>n7PKf£3 {.^S:(9pXsO>BDuyi"kMMTv D.[ߝMWLN\X*x[nWҵﲫ=wzoANU,@ǼQ)vzeX:#p&=<OVk6o~{{[ޚm-ݯD{b+S#~KwyVJ߀vJ JE/k,;O^LAYH3?j,[Bw6gc%ǖ@;6[ /ݻS <%Eo*E/|;=/}y_ǺWt _|s{yK.޵kǁؽs1J&<]c' )6,"<-[pmT#, 8/{o[.Yvg Oz+ `vB@,fWX﫾9Ľ=tзn~xgOeQdR)Iq eA*L9gsHZDR"R]?~9e9"3F/#-yG6pEHs"4P~:hps;zWv6J.T-b~`Qo5;D((-u HΘĂkm:ŋqA}9BDIH'vmMϮlOHBI$Z;AS7j ev >TfjOj(M0`vs`(%cbl~W~& l.0XGUcg9?*  RZ |MW45a܁}>n`[Z5$0'u\ݲec:[R(A2mى ҁT#= .7u3J!$ ֊ dddZNZt5KT7mJ&㙧;| otE XWoϝk6npٵN3cr-;%3O?Di<]"Nt\qչML$Yvj27QRגwVH|?SHO CWSj}sܰaC- GGG7oLD;l x-W[ 9pDOFpnG#{sGf+/tㅼ!?4}{|1x?;i`upzj;_uky߻n7\ձLaP7֭(0T,4у܄/@? Zx/6/2'/zEwsh۽*jco˕ǎ ZKJ8p{?xDK5͖Jt68).bg<)"k5JpZOfKz7H$PZ\sI=Kq)xtˏS8VDhj$ږlrؑNܺwU78 6~Тaŗ­*qnD44, HH|2 ۟D*9$o.9W;Wf # !eb^*As2zM(HÇO*\?؁ c眱Xelɹѣ7xiAZsUf ݂glkoFZݱq*Oduւjv/G/;vy(=C7 f&ZۑcN^ ]ZBJOԡ9@V '섧j1f dªUEZb,|x՚Og]M2.y_yOu >tZA2aW]\pޔe In,q '솗p])ǂ0B3Tnۼ!YfHu!WYYֱkٺQ$Y<3`$\q;ӈ "H}06 ߡ]7ilEQ^_b/ygBVK΂`|Ba)W}8B9ߏ!Ԉ%#MD8WνȂ$EM')cbՖͷN;rbxl( )Ořß]w|C#C}~bt<[qFM:k&G2]tāxm>ƦFvzoNSS- <Yh4<3 r#ʃh8=0*tXfTo6Wȫ+K']]ݏ=ؾgǏyX&&:Zj+%UMQ0P%gK}) Du0suuzRM / :-.63!ZVlMbH`Dg!D5N߻}S/5:3MOgIPGPѨ!\sF:@ۄS{!eǎ8FD@ --#kݟBNeff+Ae_엔2-(O@"A6̺g7Y{ɥO8rUzYlR2 7RhC$ўBc^g~O>!ʦ2DqH{^e4AG }||vW=TrjNoڽ]%eyJi}Fdžo;wmwkjN`bm|Hw{~3{{V G>=䂋ԁd4ޟzye<`޲ej́tSs}닔G$0v )g )Y}mtdIXA sE!awZ;Pj+w CwggL6_(>r(T01հl cqE5 T{$˫\|W.{azh*@Ie֓2Oݝ]5CC]|VjI H ct91#ǧPd ֔/×qp z֙ $H̬F$k---DB$$D"gKeZx/?PGck5&a=o} BJ]di/ҘB ?R?oQ#~VM[Tv}=vO #H1`yXtlT-창O:sLq d e@JZgţI}'\;::Z888Τ{zc2ZKEQ {i8 6~7_o_sswYWâ^]W@f~]O(Q Uq延|EA .qV Ոw!TĄ6I DPTA}p.hb< (?"`1=޳`vI*#>+'p`ZE6 " -^rwlY!.˦g=~` <ַ=0"ߖ_uoi, )&P+6I:+7/#-@et{F MP◿:F=kWtmGϻ;/K',BU~g*q 閬wlޱ.זv>x|K5`$8w 4߰Sw/%]]?a{~Y!?ZҋW?C{y!f|w'!*+;`?$x LAlz]rHusB d4dPVZ^X6WG&l?pLd֎ R- T%6QljaHhY)&!9pb˂$;g)98[}|I)Ʊ$2cRZY)$9BBI!8<ə$@LZ'm6rõkVvy~=#+r_| m@nΧQkÀXMLݔfع&f.QK> a B(JbQ^!58nͪ}kJTmmmCS )$/R|d'Z13g2Ct>/#[cVmZ޽b%H%h`IC`W]1d<_zkBd"T˞hR`U.{NLe!H7]FI]i6S, $FlyFDk02THD'NTkaϊL\""kLlM$q}PK)#ذ#&dF0[碴mݰ:Nj&7^mouΝټ踻}ꕕɰ6}ٽO#o4n 3)?/3`݈w}y%/8mxz0Z;Wc} Hr=S RT*ƘjEW)k8IOO9rdt|D9tv@JBW [o._&o;s]fLB]/V Qo3̌(r7g]]^<ϯ{ ݾgJ_0?{Xҧs٠U*E!HDLtww[&&Ky1)DH8jL6ڰa]&kS Q. l*MBR-6 :<ȩ~`ia+h+QZ2*$`@>\ze /GcmR4:fW׽M}wtvpHld}=qI~0/6B< NMN/w5EYO|?aARNdKuu\ka |L؂{gg l:ٺv:. aՋ' h.x* T(ja±`֡VJzFxyfsFtFfU[[%Աɇ~xB(SlMF?߿zUWx;RL ɥ;.]WZrbl+_ZG[7sms= #o>ڱc{gWGĎmwwضmnMk;RŠYh!6/כss5Is18$8 f,آ\Vn tl %ZgXkcUkPTN Z 6TX ƍu'v!bVjH(&WV"*+Jζv 2D6ڊW\yYV^ѾrU:i)tu7|VtdADkڻ:Fٮ7[~5J(i૟+-Ihk@W $I%Vš$Ճ3Bu`l .– bStSeO 4+otM`þ%0tK*\3ՙYewIL=t#?0e<.vKύRʓĂ*sΛmZkژlK[k8\P9/ӎ0xximmL@%F&Gyf/uM\g ]ݺR;:ڻzy;v*W g|𭹎ۮ:r-;RAGl%&&1θZ%~'8û~gO]>S^z:Rukz\ϹScn1s_åGkM{)UӋ̄߁q!1H@' Ɓ`\710#PRy [T 4v x5 $H@@[@mtPJ'qX STYұnooGֶ6kѣ۷r*/: JR >&4q ցTpT*sƝwz˿6 Wm%09^7䓷Qd @gIs@pOG:g8B9F@LO?} Ācp[jr{ǥ޿oOڶMۺ i`L(ꄐ$* R:Ys=9!ԩR8q N^́gc:Wzߋ科20ͽ?z;>g4g:w#”/uϖʙ^hNLL)hSt:$I'S2M7-LЅWg3l*:*g B[u.bT-99'~ d9&lH~f1j%^ Yxũ;eO3#@$odۭC'޿؍7g)"~MW6@0:[z@y455s|p@ ;Z,O3ߺݷm^ .6l)kƗDtqNXfK^}mFx޾n(-5אkRٙɩg͖ c=ȞGwVs\^Sm<_֡GF'cp=eg(yQ82М|/ *s@ /FPO HqzHyt '%}m櫀2A!5<U$ ;F)+WHl5 :ɬO쁁?^WzZO{ G]ӽ܍va%\ Az7~޿O"7o>59gLN iw.~=$tۻɧ8ѣ_^<Нzd|p HH6ΐ - XuY<RAŇRzmņeg"= djζNsRQϕ<: ࠚ-6%a=n|Pe1 u5˷mio9yo4l58\poR~{[o@F@IXkOp:Xp\0=YOűsLs_itW)hӰ|; ~b1 TYȬO}?7qT uZ19g$VGr׮M |}՛~] $N l@"0xԓB6u$ZBR*ָeJCqYk7X`^-c8SBǎ;(DHHO>H몮Uk{׮Pʹ\ÞO\9(㸭yEaF6 O; ou h!Y#2::=߾//HHR[VEFK'%VȀD*_M">nlN:gq 3ӳHlM8[M[26KOx:때 tXPǭmwrǭ72C&Q#J}P6:3ą#Jr5LM$:9r_.ϭ۽ /Ք\[@l7gVoki{E_ڻ_֞'>g ؉5wQvjo_-oqlYjRIdž$k9RFDV/b<'1g^' J'N_ɋ&ۋzzy\fdЋ\\H]16]ȼ꥔0;(^1(4ǖ\0=t,A\*ٙ]?1T*U+v\|*ASB%XO8;(|E#|+ށ&qֶmsBS3CG|x~*)$A!FI\V|W>MgX'A}yqm6_@v kP{'7s9*`Tda2:~/|{-øֻ{HOZ4<<礒FOaRc YD|O'zzf$2)RBJ/Jce{G848580NY)SJdEr~`u4w"H߀ˠYB[:0BjKqt{+z8 |{auM8}Mi=ɖmܼ8+}qxh+ < kF|^\W\#X|2V~0tMhfFg Iw޶^$$y[$ǹ J:PBԛ}(R;v[)EQhfʭ9FB8N!-X "!dng!9}N4OY(:z}Jԛ(OAX(EúbfLHs,I@ZQA)g㒐0z H^K@*V%SVt?xQd`1aղ7կ%ܵ@9I oay;dnfx-O@h:\F[sTXg} ,- g`\ ! $v^~7|{EElkzptp}6އzZZu޽ehY/`ӓA4 1TN-\-tuT~x}Q&l c9 P#BѦCt39+3 x'B0yܯ|lã2P'сʼnW^><0113j6d3m(e!o-dGFU+W~Q*bT_iKTV&HzϧPK1T9788*<:g9ɱUA6f:S$'^6mˆt=eu Q ѱ'uICf 1cWMBDJl~WW?bksiuu6G҂ޝ?{?8::SbT%SI;8̗wkvx]Zzڥ5DuOiɝѬjH1Jh|fCf|vzhѯFf=H[FB"/}Wv~$Xk/ O?*}˻orY;[59xj]:QHJ86~ƍHlڌRAZ`5qq:i\CVeÞqHH;;/%>/̔iNUZ]:6d: ȂIq,ېeS)Z2m+zH|6[(<gQH@{H5[{֜ /jzRO?nXy^x꯭?1SO=XG?#926Mw%>̓oz%}W:Z[Z; چ?I9/+xtgVtJv,C-ԗS2^shRG_ww~fgfޙ[Z[j!]23]Oyu%7u[x7gD0P*̙l'@X@@ȱsWϖF?~Sj,J(]\xBIl\,bn2HJ$$!DgD-x$$9ڍ=wxWMDE7]V\V,Vi"555^HY@BMO;gZZ|'DOP kJI29}A}\Rwۄ`*La-J{%I2;;NgnD5NXU a٢VdidƎq*F$yI@ox*,3%uw˹A ;>T0sl|GO}<:/}˷zcڮxiu k{,m+'@`4bÇ *w;-ȡxbu6 Yw 4}I?KJЂxg0)P\O3ICS}kz+;  ^/F{%GhaffZx]ۨKɬF(N{o1I=zkp΁M^!:GBfsщLk7R]~@Rl6'4}?egPR(N'*%;v skXd:u|jl_=@O~H|ꑃt|S ]wtv޲bZ+=aOepںR #0T Dg ߩoJ"F ||,j:d:i/ᓨDTJZm)jlU _X]>[7qt _OKBgh+yQe*݂rhԍǍp5#hg8'&'fgcʁ`ϓQ")^{ym}f?ق5E F``TyFp1 3e-xAH Im\ulaX!@po{nzgMtmMP!'ܚ}--9)D*ARHAJXk0"LH^MQzdefclִ6 "bZgnLqtbcTsT*U\- NdW(dбoMXH fhkBX猵Yz X;?~gJ7w3M葳 .H20E^2m9x{WDq#&<#(F#ᡁJ]i4 Nc`W?4;ZXKp>ne0 !l7YfusvF fg0;>[ma-fSA+KH0>19KP(kF}iBI*B$\VC)Z6  -D|!oI8 X33U~e2cvʼnΦ͛ȮB]c^ڌ_#<[bhk5`̖؂0@uF4fo&PExWvo`I2|U\]EԼy7>z}̩[!!~~ޟ^s޳Z*~18N잜)26Ib@(Cb[[j9ӫJB*T*K|WJ !bkcVߒoge)Sj9<E$0IbI$sTNAҔ?sݦ!\Wx)N Ѡm B)}둓L3ywͻ1m۹seocR|.$_T[CwU_W9ҕ%.fZlNLt61d=v+.!JF%a\NV^ Xo<.mƞ?DË쁡 Hl>h .E*+ vnXr(2 Igw#]J>O:(5[Ȓ\PS!$ygO?w[ V&[zZ;s&ITZ81<:m5g\imr?ΤRR<=5}s|/UUhFD%Vu.'ooMk]&֛p-cK" 8B D ڒ`X$$םvm@J>z_|б/|kB.o;r? |nxh"©Īӣ>3{g2U]鵶Q0MS3r~/ӊ:iV. +O㳚jܾtM:ffft-ih¾U=+Ď*Oz{vך^hƓ^R (/n>m2|n~;#B{{ɫ6@%§ߔ2xLٕCm6Ug%iSTu 5<?#cǟ8뿹us^vK/>I_+ܯ@>[oiOn^jG_0w cCIonI WWnX`š:#H7wqr|ߧց D X:P fłjTTW~j H(!Ium~qU"MRF'R bR@䒚\v@l @ ?-޶ ^yX$@bl~t+#;R:XXxt`>s^:G<4TfD +s>97ҝG.!e9I$ Rj4]l-)(a Iw;\9 \5:!Df()VX"x͚uXZ&&әL[{R:hUQR-N#Nb)(ક+W|_i/csO?|6|kT(a߀b+Q\soA _WoJk|&vqlmpKN(uP!D-^&Ӗ{zop6\WoE>/ %\z˕oܷyƞIJZaչ$xp_ec*t!g%FVKI)$0'5 sl@ J ,[%T|rfGE9F>H)1B)AZO"ҺuӮsrH>Z *3O( gHИ{c. RD1:Id@դVIdGbc&2I1[?6ڳf}f:)Vr-N.9wa RV2Y˔wŻ]zN\O`y`bEʧZVm?86:Rȃ?ޮ;|Ir`?o[,N- ^K/0(I N>hU\[z0G XNr{{sTjmkn횎ֶL&UuvviǺ5P LVgeڲI,l IZ@*F"!N8R.(Z R,Nt:ښKXզaoؐ#h^#ST1].AX+j Q ,DcR }Eol dv!;RNRW(eUPOU6 %_*D3w+9 NZ R*6DW |p;nYmkGb&_pͻ.T:F΋GoΖ"q2驑ǧ \73^߰k{w$ n wye0a#EgG N `&F)6̢66'GBr*C( EF{4  ο>SL7_ɋў#dߓφY#\y[.-ClQ H-G6vr{k#,B⑗ Py,$|@ G_{(n3 kZC'ƶ孅J @`9Ͷ1l VLم=D@tBHZc@+WZ-ͦ[FġHjd}Ȯj#G?dH+_!7+*0 :&Bf9m_;9=)I*5/:D+ֳHG2oҷ iIJwl5=Dt- T艶W_eZi&I ?ҁ` V#c$GMSP(x2CJ,I~FZtV*7m Zq7I?uoFi6'8DS֙fČ4 N"=VPR!\v^#h4VO\-s <-JJZq\ݼ]x'{{|G>.z2T:ܼz D\'rcϹv8b(eT̤bDQkX&ᘏ =ؓ?uݛvo۴XLݼf,9DBn*vDžjWlB@c-f%$1cka8[-{zo<]N}xT]̸b"}t]7p}.  ̯Pu'=t}lMk:m,wf{Zj'٘3wkOS-6%i`Ww ,Ʌr 9}Y::B)F.Hs [ג8^* ]JR%-쪕$*|K.ҚoG@=& :AKPRBg EB oxXjlJN{|RXUkW~pg!:$^CDeH^ 5`Z—B!AԶk9st?w}svֹۖ\0==@GqQ+a\khW>]t׎MΖM^6F:@j1]$͎%. +ZXK!f$ F:oO[~x݁{ض}f:_]be1.˵A ) _C Ɔۚyht%#Q4 8|X)J 颋v A{-CϤB6(hvQ yJK8Og;گ{ppYIK*kO=1/𶞿S2 dTds<ȓaÆ(Zۻ*Nk˕{k׭$z*n}_&Ҏj5lk ZONNO7lXd\,NMNUՎ=RzҒKRHjmgZ\%sZ[k8/M6[tbl-Z;IhhxZRuY*EDu`@(S%t\ў5F, b3T%j42hh ҙR&!0|:J\5;շ7_}eRA$痻ED|5RgnBƾgA"RA;LDHrZ:cCIlgK1#dc [W>oz jLJ:pj^gD u$I+O9_F9f$\~?ķ~F6ցCmG~ : [ҹn޼9ɉيFֶ|G{O.vΎU*+ ak\V&BuwaT*eSN\.K$0N@A?99Id*i榦JZ $jAd'%35-gp կ@NLJzY j`l5H<_3l=3{{GKXeX̾fJ*cJmvmpWwg*ulʳӥR9/Z;?#ŶޕggZck\- w'lNcF; D-)r8\<S,ۅ[d@4lW˿烷Tbw+/ܮn ӗ{W\~ ;֮ܵtb`XiK Q8;#; 'v@#g &1JHQʚ$;xI4(De999wt7%j/|K_w, Ŗ$1Zkoht&_b_q )pWD4AVI} L1"K֙rBѣǞ{Xz23?9ElBw부)^冒P9f^%f rJ)`t4riZ~#>YSs1@$8vrR~v ֳ)wB۷]?3=W.saVvJ!&*J]֙}]647ӻ826:=5[Z@ԉ 5ki\?;Lj(Bc bssspH._\QlJR3Nӱ"(1z4:2n u*ZQ*""?yZ vFBzmyOO:d#EFY ooиt-5csPI9/Z}mB8\(<`5%:)Ķ[S/Ư~S-=͇zWغkC$ W&d,\Pb wo7p׾ͽ^l*jS-U,Pʅ2KqqAT`zl`f |oE_un1Şwv<nj"Vt̩|_VX$cSj=9gXD}b-P-M탃#;dDtBKsSk`y)g^/avdp]SkMB?2Repsؑ#[n_ rM7lܴi&EV)hĿRi1v9#TBhE@ *ʎ<`Q}?R޼ijycN=Ǐ,ZJzȑ;rv|fvFj6W|C;vnljܯ~WZs+!P@Yᱵ tNj\4R"mm/~+Z߮7==111֖ɴճg;LPOL!hDVu z0(B [tdMiEmپȱˊdVXxMe?~Rx`n&fC ?Ze䘯q(ؘ* 3py@RI!8(x?ՙ?Y 7}3ʴX|$D!ȦD|;bwq[}5[leŒ5/+ы*9gK"B;x@Ux5ZH9υLȘ-zJ2gyB*$6w]]JL LL ͌L^7HZ!NMLvQhRy ṳ׬ZrmKs3dMęٕVSWnTʅP3S';Hr2@uY@SD)ƷB$6>yfzb&1<};w/Rߊ{Aq]@.h%g2Eh~/KL貥( TY#P@ B'֒'oxvP!Xѷ;NMnܸťS=wߚB_#,ټ_T_8<[O׬^}!{EC/G?c#/VJ#05Ztr%~ޕauY,3Zˏ(Uť.[,`"Zz&&9޽C~`f#?uǎRyYT˳>^_?-cIk&2\E+ز+/wZv/IrXhfZܞy~+W _ )42"~wЇٽrI]S{cWKD Asr=Nۀ+$fdVSU7߼mSϼsF֢O8TWf(ȄR46<<_i/ =OON8gBP> |P36+d]ri|t&̄Bk.S 8M֭Ξ%Dž|{''''=f3b)k5(dRX2Y/ Zqȸժ"u)65!H*EFBH%TQ8K(kABho/, :\hFk \B.LM5y5UҼCiy #):TZBH(b(p ĨE 4i o/?wQ|h{}ݖŞVFH 굚EQ(o>%Q";Mo<^>'B[b!-+zO:Q3@P72~l`VUS<:$A BJdpD ^d(3bŊd0 ;{;jQ#{!2MO3:cZ0=fYl*bK'*K:k'a&Df 04]cwPq\[նn]3х) *PҰzJ,Z0uT)Oh YdF qm׷o_ջq5 Bպ01JJ㵖_׳ͅU?A@gSo4RlfZ;$ٴrɓCݲr2XZ_?==y䑬}=oz=~=o .B @,%M1_jeTD*V P7}_ dC֘N.d-/A` ^ׁ ]) ǒ$`cZcf@(YGh&ؽ?s7ܰ])eeC1;$LL LFx ^2)JY{$Zk2vtbDLNszzvfjw㽽wlݲ:4K#&vKkEəO 'frK‹ff3BH-?mR1<43[֪< tXk{'B y 01!۸L?6.U@(%pd@)Py5? 7X+AX\ܺշrxٖ]Ą:uvnbbfV[ӽz%6I\}?B$*֖+RT!EVlqzRڋ#r/[j8Ie-E(%Kq1+TJ((r$MgfkJyZ91.Z-A4Q~.IԲ ¼1XRR掉y򴲶ƥl\B(D/=ar}Bi@xlI(RK)%@T9pb `̂ @lktݑk͖?f!pMSt{%    K"_u/".UDSA[sG遹 @6ݰ{]o> 8 qq]@ &p!j_z ǩpA /X= a }DTps׀."uϲ3|C('%3©S[;;v;a yZk]l] fh=?\ yNP=u&^Pd #Bs.$ ߚ =ų|Idt .b@pl6鹙ٹDj%S5>F8ŖA*^jR&luu.%ē/9H^:|?|zt[m]ٵjʵWmPCI0ٚ7+TbR ڢܨ?RecՌ`; LD PV Xt.m=ӷ&ofo|[~ ?‘c VɈpbj%kio[JzyA,tgkm;W[5A |_⪾ox]8l|?ؿPgW';_D.MӶf-o !_.[eҲHU[6r`b`OO?940 QP>w}7gJ]ˮJ‚#J6kݰ E#F"vڗ1ԉظ(D(%K  SV"؆ojac`?PZkKsTl!iㆣ>BpRIGTpɢ" ^$|a@~Fi*agpUvZD!JBr:22o#'&@<dP)% Q$~Ֆm[6vw]p;>T/ ŅS1~ߚ4!_}sZ*g-ҚugMk?K^:Tw{wS[KTRZ9 "%8!8pnyIe7fEHOf3\Pwuwwv|?=y^wzՃ !B{^cQ xqO"rkQxmk2bž}/W*׾G7mz(&ϓZ-9- yZ۷\m˖$3:l,_erxF_6SPAβ_8`eұFak\}=p7+3Bk#f\>k)fҩ1:%z̖_y'Ϟ9Q陙\&N@90ilR7SZ\&HMRsM_{=󤘭NZuli޹d>su~kGg>S*3_&øk>b'=6zإ&<d*PjVn@ص&_']qlٱjPTMUGOgS45 GBg0o|W{zڧfF6l^w0&pDZ"iK5z^׵33+mZt1Gn:".lGWlF@&F!/_tB !9k5W^j}Q0A!|Sϝ=83]zc PR^=К2 cS6w\vv)/??400tNCZiʎݚ~:c eKM[ 뮛c#ҥ 1u &X 9/:B>u)1h? @D^Y)uYÈ9ƅW;8YCXZ0RFW> A,;PflQI5rBvEw3SxH 7BXφh9nB#)._1Gjyz)%Iv>ʤ%@1Ʀǎ6]ᖛn=}grznJ (ǟ8H4f@-6&]_r"#";p/`|9( ]9yOHB J"Xb? m/C_2RD!-5idlxrvnlvHKcx2޵w a>A\S=R(뵒!.67uL /tN'Oٷ/ą3ΟUt#sBB- B"pZKGe5ZSN039rZ/4anf(Sg+8v|[Z=?0y^k[20&RJDP2dY"-U3Q ZM~UZM?ċǎ tv?q7ݹ33XF{ƞpu_uQWZ6/`$hlϼ\rZ3h gH*&|o>M"w>9 ׽^x/98|M5hvnvpd$796'퓞zyԜ#G_;>kRlٶXj'3-Epv~&ƉyK %3 $-348ɇD(06>B]{kժu_wc@6OM ؘ'9"8 0sϩE#+J|^n9) ^vTw!fWMď?C=EF1gAtz{{뮣>tH jUV{rxdpvn `eJN~TP-+_cp d(B@r,/ ̻_!zPuu Ϳѷ!/.סF M6Ϟ_}.E7tY995Zlm 2D@(!Dc~! (8i g(%Ӻ鼛#rCQ-\,/vZ(.,L 1@̾M@KAJL(habĒ(|?eLcM)5Ư0 ּ|ԏ0ҥN"`j 2tplx7vu̎f#uP'7m\hm()藙! oJu)&]J}nkqUX0ƫWxEC4eu DC3 `釞x==L/| &5 }-}go|[z[RH BоWh[ե25L  -aNKNEcQYz[~C 淾ǾX3ĸV:{SӓIifuQٷ2*[jXhmnO ͅɓO1 9}`$$HqcJS)_`*bLLSk<@/f 8̆g#,/_l1`3EĚ5%֝ݡm\q׭ݷ!!G˕Hj/˟<}n`xdeNJ(MfRZ f˳hړ^Hl f,bv&|ѱ۫NMg9sOwpuW^A>2FL (%gb 'l &[Ht_,qzYB2#-!A+Ԏ({WZf͚|6sî]+ZGHZnwnUI$v4M}uJ+$kłԊf&F P@A4 . jR XIbkO7oVȏB{n[_w䳏/@Yc {jvv|t40 XɮطBXBT9B fDT#uI Uݥx qt4d5.CE0B!liOsIzZ\R#3tqX8&b: ZטeW|_ra7@ZrOEXFWm}+ښ23>vPHl–ϝ#kLĭ^\;s@&̔gjO>)$+J(l:f%(y7|[i%FJ:2kojotS{)) I}OeC_hkR)m6bgW*uǠg_\~Գ?W*W ZW弗R'IEs QxIs(挱RȜRLR6i'<VYD/ ^(=rmŪ6l淼ƛvOIҸIe[z`S&Y AzL@@LB,-%"|D3ǖ._P*/YCҵb[01xM۽{ob?3Cm6姟޳Kj~k]5|N|#j}w[WyGw_P[s^B-ii e%5 T[WFS'=Z( {睯Ft7r}Zs,Jr>}D 0\#_ΰ^X޾C Ma87W=rPOpŗ^<6;7 RA:2 "2/Zr(<~b"Xch [5ƸŨ-""98RWm~À q=Ҳޞ0L%JJRБM ۼ Ui/| 3:FDi%ے~fpII1JjzT}ARJ"JҚLϊ0gXOdI~Q,heKT#K@bW<0$$@X,tvH7l_7=>[?NvcH-zX.¥.^]A\2K% tC Ǟ;gȺ/4`aRAM-ԥ[wo= UkGkJZr`Y!?okjx@<37wxdhllȵv 9e&mOTf3gJFGz& Z@D\J8J(y|` G_:_E(%j|ߋdd%I1.riœO?81AWwڵ\۷lYݷQjn)J\R}O"gæb<[ BVm[:J?qz8NBTslH% < ,rD( a,)YLhr*?`gc*ܾC/g4=1Yobjwu{Gw|1+s![jaL_" r=./kߚ|;& )Vk 12 sg^v5_>!_˕wGw11M\[Z[Nɿط@bR)$ ,9NKj)6oټinn}/+^WVt{{{vڙeÌ|Gݳ?eˀXW<XUr4v&b4]8B@8 Hm5 Q38J &_,_`MQ[o_z3>QO},QDyA+ʓC.rm}7Di`% D +@!dW~X2ȟZ"2" $ bRr*O4 I 4:W+3 p*Q8kĂM^Rr-RkqCz^~LB`R*זxC_|qͯ~sY֖CeTp,9~~qٿQ2PdP_#<GuF5}pVo\V1 lY@t{mlҿpIW;6|>Bh$;tJ.]qϸ=`ĤQGiIRs`8Gm.J$ Q l4*dVq>Z?M[VZӻ- Ap{ad2Aaf|dɋ<~:| I9cl&2߰}˖ jȴ4[ LDZI`О^vuGb vlY ĎNlݼ*ImlD֑aLc ~3B)B Dfgj|H? 'tDy@oBָh׽M7߸j٩0A-$D10 oH|_5_ 2gV ̌R:rJȆFpJ ֘@y@ `׮n Hvf&f~o>w7vH&491sy-D/}'Jbwك=wQ2>8sͻΞ?ݷDy:+Z H<7Uk*:UV+yf]{߻QJtW4QY#e[ Y~(TJI-+]@4$:)&aXBKS?Q%O_CІH a =+sdo{kKssG~[oV.׾nڏƯ:uҺ覛nT{?|,iV__y'׮_513c[nYgm^Ht6Ty JD,#AB .@2G0i @Ď %c]R3 +&&^{6ߥ OkCV*R!Ssӓp1}֎?l <*PAbDd86}apI.7uV) Z`<ϤyìqBH$@&5q˕ d%2"Z?l,~I6!"60 ʥN.8!^% DL]y ߉l؁(B" 8?9,9 I)6aVm_'>JY9 s0?'^d$z {4Bļm G ՟yk0 g&{ ~WЩ!ҁ `0:%Z48Q 7Fb)>J"ڛoy [yiZ?YUкT,RB Ic!PMLN1{_:#{.-)*}b)QSD@ӕ[M2ITRu=B!bBI29aQzl CG$Vy׭y'_{($[׵v9#< d! H֐1B"(րcyL .W4Y1x_x@C/=%F@(Z=Ru]-[[{FO=U̵>yVOfY) Zv[o; GǺ{vڡ^uR+ͦLwӟ=oM eœ Y@ P(Ѳ !_CNg _ҾFMWl7Wz (ąaz8g11(#@&(cIOP*4Zٽ|gE_G<#rK{k0_ àP( vo2/Lk«[:xjA≑:Coy_]pvgnʞǎj]ZiHAP3 `S`01lð&B$[g]U _z\ V^oenvʹLFzDR0T~4HYlm}7mjio7L);{qZk0 1j&%6\,n(ȑҤecBUl4={ԩֹRkEoϊ\&dH}_gㄲѷ5U5hG\(0o۲jb 40 W\gͿi|)cGJ~޷}U֞ iB2D`"Ikv12R)BrP>}Q 4Z~T TY @SI4#.+f%/څG a[ |w;:UL9tbtdl``P&ON#.5+W}CfCo˕ᑁ;\ /=\3B[R `꾷;> sР3TKe:rSy2A 0ujPl&?3Sgsse|s숉%K%h8ԙ{ySKQ];V%\{[nXvc}ى{}SхB9𔟦6v ֖R%8|}ƣΚbT NLL?Ͽ'r7  P 2Z Rc=fjD6NAD? ڿkPF!$LIjXy[v6e~Ka=Ǿ'ΎRLlYrрB:oaP!]1;<1Gr]k{ly^ShILiZ{ZIDZBI%%SƢ@P )eD sL9|YsKnVufAԬLJRQWGz 1L"@ba.]ԋ 9h- 'a]7&AK\T( neT/Ԣ$2odt6Opql`h_յ P%PA;4 aCx׻^N>$̬$螷Ab6u;n}MeS# إ %Vҏ Ad"ILMM@J H)܏4MlR.W1֪|Gͥ3s\n!c GDSbRזH M[:(\k[a[Hi5V{Z)HHTJu cgOR@mٌGZza8[mx*1)'uÕ@LWj'fϹ4@m3/9WV;'[ 6"C`ڕ̄D_;gL"!؞̊εLZhnBm}fՑG;ʕ}/u;PʡQR&ilp)k8-%9~@g Ȧ( T\ՔM:rRd>_|?M. |A(7+gVv $M&u/9 ~yӫˬMpaXr`^ /0/dF%% h]I5"yluI8gk8\;;pd Ri6lx ￿K_zwoכr79{鳹LSKƁIT*A\JtXhcDD^;˫Xi*TJ |/ˠ07::}šL5!uBHn~=6ԪZ~KHx;Vy #N?|o7p{^<ZBLKN&'&gR@B$NJ $R091GI.kZj&p)@cIt١R=Z~+i-[ qR rVQi;2C όMbI * G>|bd|*(}[Xra&i3/5F,<BBK@4d+Z?}3g[ۺtܩ ##~ؽo^u+|ij&9cl,#!"J"~ +gib|Kb<λ[J?}w F˱Wwd^4k ΛSbѼ-biSsSْJ gϜ~G~o}yl?uIb1kغ%@{|~mߚoBWщ fb^ޕ #yˮӃz6O@fv:₱"|ZȾ&]#*D5\;B$q*)Y1#b^ԃrPФ$狞 B/;68<69ձ'~АJcR{ȌxBl myƵ6qr: I/H!5D})%9̈́Rȭ]ݑnް"ٻwbJər-j6 jBD*tdgL1MLTWp6onjrIHJPaRqyJ&{:+euoݽKj\u]1ᑓO fgfkZZ=6eiL2*`r5"&ߢf՞WMRo?E`3s%hkk:ym9kLaZsY?ѰK`e-} $Gї/|u oI= 1T:k 2c2֖^_Xwy-w욘iot owO>LRqJ=wPͤj{m;vZjQ\uvk`MZW3Ai顐?w ^8m _RkOJ?7׷= f DD&nH N4φ~;v{{oƯe2c=r s^nmRCl&#\fu+9yn@+O *ήm{ad|Z'yoa[#Fv/03i", O=xvxCx__o\iha3\L"􁬵VJ՘KtI Sg ])^ιi@U ԰$#Jltۺ]`ÿ/88up@ ^uzjxzOS{J\jݷBsӍ7cg3 7hTN3lܰ!̞=wn͚5ϟ??,{r{RwWMFO:m*ݸֻnqإQUf:2V(( ilx:5ƂZJn†EbhB^^^rwY)pY|5CظΔQ2c٨a`oݺ{WTz#xfl^F.B;fq\7g1L.G>^=^d[@@L֘2ؐ/LZ״> (%%j;?@e@\ ™{{/?dGR_/p2^&,&2}omnض}qC)c5 |k /p&*t-j*%M,'Y:01 d6n?wpmֈɮ5^Alܥ Z%iZ.W&"+,b 9pu)MVA @,QI\[2i5;vܰqyMo~=iKӪRia  m[7[zZzB!eWWs|0\ '.^0:R ϯĦʑǁ) `Ԧbc4#-hDF 8rzo\fڰqߔv,#Hc r-7΋</Ͼ^Q4*ܱe _m{_h: fOH Hy|&hcMcMXvi_aT$6fjk|}crb|48AWZ\g3CAޕ{_x:v3>]^wmȀa! DV(vֶ:-N:+41:X[C:Јz~"8~׿ɓg H;%(0_*/hoʅa$Z{0-ܐMw^cOwmbbVG>ܺu]Io |@Sr[;^+to?00fSd^xW8̹S}}+vtcT-ξv|W&NF @ih xiӒ^ ؠő dcb LI7ttz7ѧesB*Gdl*`!<p <91OEl_Tj={W׃|?R*yc5wڵrʳgϾ> ج/9c*cdаkmo]w[2b`zP)$2BC`4хXrż ͒YF@Vx %[?-Y=細_]!S#C-};glb{ైeJ}}_}'c[<7^|q;}-s21ujQ,)v"J(U#l$4p5BYcQH K^ŅA i*PZψ:hR ` l-:y'O;{V)CPjZBۺᖻ^xϭ;nY5Ԙ %=%/󿹪(6OMeQ[vWt\baM,fl64f|!/Rq\՚RRsD(Pc|>'OfIp443|#GDB?ݲKg3@,J==Jk뜔KŅ,L\~h0DKhR*XHZ:b@Dre_[Kk\BoLYWv֖v(8OmRJ '4-H߷ԩsΞ9oU}.RQB)TYpJ ]-67=ރnz{UOotu\V!7o[հn*S<;8}a/s;53w;>8Ҡi)3/$a=722T9q|5#FGz\6G DA&%ָ8JR'j=JSk )a\J=招l?kʆ-?ⴵYoݷmw5(|yޑ#N@MeZd2eE tmH 憆&{ښH##L\(Zlް |ǝ7}u~&èvꜚ-6Tfz8 &?3kj1Em P) fXؐR{^&qr==kut:, |asixJ[c9S䘜+BsWܽYB!5+|'S33SSSSQX8IiXs~!h/N!0\w=I\gȌΘ|jZ SwFFlyldPkq]ߴrdt}ͺ5 PV> mH r+eKT10efF! dD vݲGfbbGOwa$1@3)m93JIGHcFLd}v}vRx "ٚ 0&Nc % 8 uB_ڿv͚Z>19|ͷ\Za رsG&(@JuOMMpnEL6S(ෝ6n}﮻\v-1μ{L66pd YiY)"Hvn[k,i٠~#Ę6oCmL\2֨5 D2tr`TN**_wKu{zӴ"&)3[X.ܽ3"6.,M 3J%MR`b!=v_i-^soZJ% % 2fHIcZ1aҌL!pwXp؛YYc{ˠI:=6,3Jg0IS!mnܺ-nUaFd4- a4eK@ 0Ul˼^ki5 DuC~{=9wܛԦB%(djAK+E (L6q-RFQ PJ}__beZ{#tuv4M)]Kj'L&7βd wTJE4U@ud3*l`Z~ E%9ur<[#Ϗ$Q+1W}KtgWwTy@i҄rz-K+L"LSf\TV*u.p @0ZqζDE1 ΗzAVBFY!%%kT׎ 96֟Ax^Ko{Juvh▍;ffؤ8j.:Z[RkYjSsj@>bsC?yalo݆J Zc'F!pAS:7X=;iӦ7S{GKRk[;~LiĉsK%2*PָT F&"v.15fvb`w $1JӳsO,|۪UΝ.C3LF& @)]KRĦ DɓcZ套Ne-{>sD [3 ,VfGj-ȄRk,2BK` 瞭݀>LiS'?%|]B"~BY[@k"fJM.iKD I  $s)"Jݷ}o;I5nm-4x|h $NbMs 䜵(0Uh“ Sk@̶7FpBVB5sutU;-XJRR.(&1PػuU-koz?\` )E=I "x٧pGϗ~bGPbL6OcP~߳g"~]}+O=6;7paO{ݼkj`C6l['ڹsʕ+z|̅33rilloexG[{\Ss'8*4Ml+ Sii_H0] u]8Mf#`.IWPaKg Zbriԫb4]Idݖk''fGGmXfÜffAl=J@)eGGBd<⅁v]f?Bz`i)=PH +H5!`@,*ʬ$Iu =VJ7 isΥT4P/Gv);y—m;;1lX& |>,2괧dO!ɷIݹ\,*ӕkŪ'y~d|gd|zzN/(׽MȀ @IP` 7lԩ'&GpC_OsޗBMLM<`N5jȄZiPR$3֙X0G@!#֔[zk'zl۲>MMrerj\IcY iP $qRq`|բ(ZGRJ "Ȅw<[|bϱB8;;y ϟ<~85;o~©0mܰV3@9k ! aXc &X _#jW%`PEq̱U \zy@׈8y-0T9SJ5W!s~&z0Ӥ}Q!f_-=gh$x&,MR) FBH dg[j`MwzkO?E;︳Z0;<_k>b;`%J"%˒UN8?_Ɖc'vr&۱%YV, @Dmf3eu83hIb*y g|߷^}CX"K&ZQyk<T[njukTvma CCWjv?W\ R\:$98d`RK@L00*%.~^zdF{Btvu{%p'ϝ aPkY&2l:_oş= dvn[ųuRc!Q#-f&߯7ocXm1rA;я!2 p  `ʭ+e/ȝꞗV^a.]j*J&uPc(Xە=> 6ދxEDc^J햕Wo^1I y$lmY߳IUj\.#"ɥ &t]F9 .8r:/djChd7 ܒR@NCk6t%tQ[nqM̚]1Iяq֮!F Xjs@>zC45VŋGN呑}j~+W˵z}ιd~͛6o۶m/++/0~)nz,ԥIlt,pr,7QHC_Q]3􍵅l1Z{w;rd+>iYp={#dqDtF[j Z=OAv0 ~$# 9 J#B՟?>|Sw  aFfk#⨯]_6`1A -`{Ah;2]%〆 T/+3a힅<ϻ41ADlX+87Vjbw<3ʻ C7?@{g/<2wpƜTJ#~&=cg \l|Kƣ$\p v`&Vؕ!K7n~g*tf(`]s`8 rm1m =z(cL2 ^wA"r>B-jӧ|ϖ˵Z4v}iR tj.Ԧ'9NY" |v6-z~߈~הjYiHǐ࠶HM2==VzWK`dmTl451NӴ9_rU^SJpBq*SlXҥ+X0fFj5>@7޺k)(:GD_tE4\g0)MS193N0αգ &98fD%n㄁ MV\'? ssֱs]#oOw~k.g ;D][Qᎍa.+vfL+}O Ok ,[qn2^M8C6F9;z<7ٵ@:~ڌXy$MOU<\! V6o_C4YRR6ך!L-| 7 tZ>lFPъdf|VZ}DtMhy6`` BJ&rρ32`\2J3iR T%.jT ~>0 5NX22| \S ֳܛUHU`[^㉌쓛\.gߛzq#KBc.9u«7/HZ-! l!M%JAiۖmˡ%_y56l(v֬YA)MO<'8>?zKř5kt rzwO6蛘WV+Nr Q c^Z*Ep$D6ei,tWvn9gSd9t\p$66W)g?ӿĄVg_:y^~y}wo}O]Sqql2"w2"JLǦ6m7, BI9Z%7o&9K !DD,YgkO+"B@ DLF9@'9?lsk ;vݺS׏,[{zzV1&ޞ3ߎAylF#F khp`nXWc`HZ8cW ~M~=z#YcXd!g`)8QGsJb^ѹ7cƿ{3B YQ tr':+7C߰ۧePi(#Mw\*_YRMGPs:Pqcyy#2B$wluGV؟ ,[⩧>pP3g g|vf&*ɤRN{(s-BB3j2d|.kdvfrj!^"m4<kő41)z\7)/^'x~AA l;QjYA -6s]K\$\$BpH'&Dhl|Хt@i.a wZn].IZ3Sl՛oظQkDQkǎW/ -__KS/LOL7;*j1 (=SS/<fÛoE[sV/<xko )!#G\ d k#8 C4E0&ș&wUwe+CBaM[wi÷-`L.]ȱ鹞\))TG8d=/HbWieKKO{3W̆ _i6WtuaD!1xz\tȬQwan3?r 7>¹q:ol0]\@ 'JVve8ƙK\A8hu !<g3$"C.%Q)sg8p ]|9jҔ-x/ Bs=Jִ !D?3bqFhY/l]vs={a0ql̙3i)&ikIS3Ύ,\>w5Uir2{7u]_W/_9f'˗~_ի9}Ma=Mew㧤 IHi'ۗXN*h_x+;x*p.C˛>\ 1sn"7v 7b ,ym~ UG^9Y ij\.DG1S 1]pŠUfrLءko>4;y.r2Z vt6''kSO=uwwgΜ9y\em};;:YN*@gR:LzG 8"APdq!yi/d@itmpʡkEwO6W83Pl611y$I0LK2e @-co6ḏǟ}NTڊtMZlqH)$[]sum{4{!8"MLlSF# ^Y'Ig< h檾d4IjPHZy"q8K 1κidT硗,:$IZ-]:ʕ7 P){h+\ӳZM8H@ րiY$@CTb4,Y`کZDE{G̈́v8pa;n/Z52<4499Q,v -[twܺ2C{~$Nv3`d*Н3˚Aď__7\g ZiihFwWJ҉Sx?ݺsͻg"rtVI `C˔2x&kAG fK2疁%7-eзx5>z}Nk;o`^rzaw1D?zTLcm[&&&rAvuO۳g"[~gp­ݖfWkw߷n>F122h4Z{^g?]vuϝgnUV;2fÀ|Rjri^w~;ZhZ'UB:RJ$JJDaL8n&gN g/MԂy`%(YĖgj%G9댶5+6\W"($/Lr]t W>7q+xVkqԚ{&#sǿ򅧒$erQ{\'QήȲa|  8B[ˈV =כ/,9qΥ' 4L<_3"};Dwj +fQk6?Lb!䥙sjheaN!`֡};Y/=S%CzYpsx80/쩓`Q<@ttA<CDD , Àij9B,^V$IM BrΒs ΅")sqh@m9b `:׭]ܓ~Z6$ŋ Z)IG$WD+E4/+-#cB}0gHW,. srOoםwv:rN:K Oc@҃ԀY!klI_S0*u>"|x]Z*e>[t_ha#4CC2r!Ϗ0(McI_Hnyc&6!xBŭfPq۶zV+WoXaxxIZ8fM7\rϞ~/̄ԊgffVYazm-%x .:֬Zs]wןq,('(mYa'U \ʼn%@{ByBνcYBT58f PJX*:sVEDiJkYpv脱 g},U4ZkDd}YxA-RGwo_cZQdVL6gd y&D:׈"(m 8㲩lD^'֒b8c皭F1}Ҩ'?c+_4sy`!AS;W1$A<\\#ث#|]y kL892SO7VΑ'DbR" 3`@S릝彻_޳o;o߱muV_wryo>nmnr뭷Xk |_5ݿ{owo߶cW;naW?qcOLNq1q=D7۱e;KME +d?8"%+9ph4A[24ԻvC&I4X"2cd:rIK\[ zV"4=sfZ1B`^W%@ /k VЍ/Zj^nx+:ǤD|1Tsсcc;vQ|K;`:jҕ7ٰ} Cx:> A_z_Z (IL&;44 rIT* }'~ŗn,l=IZL0=?O ]Sd3z֨EB^[mm-ʠNߙ a'kn8456c7d3g,hFB mc.JgݥN2V3:uӃk<9 Hpi[ַLYZ;f[g6m@9jYx.01srh4|_`]tig11ٳN=տlHu(=/7jcl.KDf+Iuoo1HsޣcLjAwoWZIVpq2- ]De6kζBYÙr5u |x+RY ̓{92vOy[0fЇ?f3JoX/ "+2dm#0ȥ&0@V(Xkswd* YV΁2r3׮ٸvsjF/;qrbr:原:]o6g*d!&{N!g!rq8*FWO8tD}ZtJ a&rqct9"!%BDrNJU \%A$}diO3D 5jՖ1RgtENkVZcR^Rf394 @2`Ǚz\o{tU!7n>yT#ekrrrRafVF*e rD$8ws Vg_?e$j׏]pm^W1q->:Ar㲱rb S4Ovyk4ķ'~cX _6c,@y%H$4X`&BOB R,8H6cE!#Mx6~,vZqV rV 9}Zϴ5`E:<騽L,4gD,Y`\Ņ l>Lz l۾Cq8 0ƭ"`jԘCD9Ҍ3/-e&<)CLp!d@!N]5xy4ETb0+'k%)sFcɒ%Dd -fQ<=5j$HYk /?0AxzLS'V R 9Gp A U*ʜɯ|0^|@ިWw}Jަ-7yTQjLG-D8qJ~f'JSUʴ2VpɤdR1dp7۷m挿\ٱHJ [M/;\ꁬSVZIØZ|tvvuBJ!E{\lJ1p$'0Q[m}*$nXxW.[u?uuρ4xp ۦ W)8]o E赱q+V\S8waȼDF3<|S ;y|9ΙCInd AAJ l\aju>wV*s*I/+??{W^? 4:qXQ;x͛7c񛿱~j5_ꣵ6oz~|S׻ (=gM7n?=癦U-Y=3?L;\d\|C^=C|=o5_ҼR/>=Gv<1k60^19u#Ek#.Eb~-ܴD"JTn%"6cc\7 |G46v!nA +۟'7mC,s~덴up0fZI]ĂR+ E+2g ڌH0-˱mOe{E +yzj5ąi鶥l*4oex[m-B:g1R}B" . ^4U-e :k]Y"dq,1l5fZM#/rڰ T4=@‘cq)c9y jboɯPuc&4rRQKCCF#j2LسgRC3n-JN09e=ڰ$K&j<5;]l6ٹjB !QNMs`dT(J\Jz>!Gd(evĈՌ1h#~( UJ"sNT6n%jjksftt}94VHղsRVGj)-sňkch"2LО@@\H'l3$q1IGg3y^P)7stt.[*JbL8'h4ߞ'Dkq$~p \!flo5umݴhù8?:::6i,W+sJQ̖smJVH @OHeP\+9g_ 2;#}=əKL†ֽwLz{6mZ(0 :ul.zp-qz^Z˹dy¯֚LG\qajr'$LF!Bp}=xuԉSŎ0s ?˗q27[n6KGFF{kj-[73v??:z.>===f3s߽4ˎh<{ؑM 1 fh],$ݶkҥëV/3(y Bd ݮ\" XkRl4I}w>ڴ-1a`[O#NLOW/f;ſe˓7ߺ*@;?y,;vxnrVi,'ڀҾ"bmlW+s6R@^r:M/J Cr`[wl|GfE0Bp{w`9IDQc|&W̖T6j8y꽥R!B!9Fj]`\wi-/^qr2롶/\B+}' 7HOvuV'~`frXʃjZ-tm2{ø`N{ꩧsPꭎ #jڋٴ )uiR~G~6oD;5_۪<_x٧~N` -Z{Cnyt5f?W=7UE,8N|/lԣə^]+9PdsǞ~쩨 ͕CIT:"Uuu#gYdL˧\hQ9R@jMqr9y~Mk %η⋏7&~ӕuެu7-wxHc^W[\s%c*҇lվ7Μ?hMC}(9?ZCȸ`IfsM"gJ3ٶ* .ȹ8R˵6{`r/UgHKYssel.-j^&(45شXoD 6KI[g䥶6)щ绺 wlNU]~?{p&.No{eCCG=@-B2*dED"u'ΌN>?Xzʹvwv2Fk֬JR猐sm+g#:DJ2Df hÌbiRJkm6i$jFtȕ5)Itu\ʥ=BS3ZW*s]].>sKV,ٺe- 8ISblu-J )FEDٕ@eawttA6lPܯV\Ύ0IVec97կ ;rcsCgΟ\fͫ7m^J6-tu,]8*wdƎHk,<"V80jz^h-8bN]h6OT;8՘!]=Q#㤯Vh-8&@  W)w˟ܰ;X'V̩߰ӈr˖ܴsp`s̙^x1?ٟwSSSgϜ{Æ R2Dr2===ӨVu&tȉ)5+^9vPGGU9d|J-ju!u\0?;M`U34?AWK."g#tmZˇ&.L\wcǞqbbkc .v*2NS$|wX{B|<' r:%/h|D ԮX#N0H Ι5j+39 C=~˟{j#9ꚪţgt^1+q R-C^6тyJcR~[r-ڊ-Պ7~n4M3РW)ddVr2mܺcIv{5F[/{gڒ~3?qutuFMl&2`20kmiTOKjR]?u ˗ , {vn ,A @ $Y#'c&Ix%yJRHrIXVJc96Еwnb)K.`xI϶-z:<[ck׷ ˕/ڀ\˸x9N ! 2r<3)PRz(>1F qK9/^ C\wceí8p nG8+bL$IV.\b$*Պ3<zj1'NdjXUa@pNas:R>0,h\z|1N5+׮/$]|8:zvdd` gȹNb!mUum.-:8^E+B-)uv)3A&SL) ZޟWk5Y@J7[&p %dx g[d^Zdɻ\.wܹO|zGG6o޲vfܳ&Q|ZÃ陫Lp7 !EVυsg9roG>2;7R#dVRzM,_*6Zz͙78;Øى\ KD D#GD$=9kIZ0ᆵ:B 1 />8sƳ4QI)gijJkg 281WJ޾l I'_]:/$V%_gqM"5h-`̫ՑXd#B(.tJfAߴ@ ,PG+J4U>Զi O?~o{rB3VU9cSu!Hnvt6aXXbx%RDl;ZѣǧgR.Xk֨DQ5*ִ&$ZK(9sZ Mb=p'Nxz;z;9Wa9늅>͆;oX, !3E Q>W[i-b3qMgfů0<484+:Zg[c ,SnU(+}1 s@ ul7? %@Vc "CE=d)#(`ڑb-0Tܨ7M^nM\l6?Der,0qVKŁAGV5mGwlvrwD&} kcf3\ka sdFl禦jITRMq &NN~㱯kwmtd|QCϗ> Lk 2zsʲ|)29$s??Ҿ7nxW|ԅԙX' GSgϕ'.qD8 .8,`mjBi~!ˁ>sX\Ҿ]]($X+~6ǭ[w9xO,_X(<өKC=lvZ2/䚉ҌZN_JFf`׮O=ё/x];Uo&J9 0daYI_:Icj"b6¬,pƒ5*M6 cNx#//= ֙=\A;r9BO]vÅ #̖ʑ5_`:;*|.Djt+S"ȩJ/;~ĭV{ȓ^K9&Tkc.31>۲eE\T .f3.3s.2& gsV+mL]qR괪 $ _V&{яN_,}]F|ͪ#K8\!z^!~ye׼ cxg?lESB!=?:jϞ=ǹ!`D1F ﬓE #5k=zt||t2sdbȢ3 k~^m kR t[`=I\WwwfC?lFY ^\j)iHR,褵 t=+?K%eV7^&yAfk 8|OGX1SRM{l|ꫠXMU֭xm}s6 yk<ڤ*zn!NB Vk]TGϏCDDp3#H#~6ް !!k#y^ι(N4W,(Moth}>ן|≧rsP%*5"&Z;IimlbͩSء&BgOOoO&tӍk֮(K!{CQp$`88} gH8]KA ZcQ8H%tu#7"- p5i9( vo=Kˣ jgOG/^2!0Vo63O44ًSu;7\B._U˂'YAۭaR?~9tf+KL/9/^?x`Ys@ 1fVsI 䒬ۢfkvzǟ5kamW~{Pvm'0S+jj֭_ml&s~O87\v֭[ݻwo>}>wV?Ǹ[gXB,PRΘ<+}!h0(wۍڜ+mC8iJ!sdUi{ [5k|bGadeC‚$Gn}ʼn ϓ@6ggO[ 7wo֨v ٺ>0 ƁjGjBs.i.2 kb#T:e}zM)d{+}X\$\$0rqVgMjKNiѸ2Kᐑ#.2q`w}:5r i@&]O B4j0 P&"@)enX!g9gRZks.Mg"""Ƹ5TLtrf/MW'gˆ{Da>ܹF H]<>=ڵ"Yd(n _h˹pc*L6dBDڏ"޷T_3mE B)cLpaEdZ)? @ʽ`y}_>B!K%3w߻qmZ$0לqZ>'e܊m~-A<[d^`RH-= Bg' X+ika%fqCWO~ xS/~F AXicf[(d6p3l.Vk5YOgV6f\qiz4T=m>T9oض~>`Y<@ CSWT&8?gc&pZ X:;G`̄bկ~y1 s&y\ԕKM=zWOO4/?v\N[oȻHkXt\N !J[L2cc!Ο)mؗs UqƍYEf ^a;gkϣ`j%gz~޺466Ҷ[/?0BK!:+{=UƄLFK:rr/Dk p.|v;vYyBdC?5u(Woi%G8utdZQ<19559AADs-2c%和`k !J@ְx#b,=/}=˖wMۧ/}ߝnizzzzfX*.[j??;t =I_1 q|+c2_gaLAꁇ]wqWy|_xw<4408v~4fu=w`gWxmݲ~ə??UJ?֬☈ H^ 'ϝW:cB$F3(`|2m%+Y< ˻k8ZkD:%L>^>Y@;otװr^`հm "$XSsjС}O-vpt܉Ga3_ Cd8d'ˠ$37Įk-jWDKCpY}Ye OM\K6[Ij !r/ ƍ6$, 5>5Jkdsvsuk1A)8RZc53JF]ZJ'iGGg>\ !9F 1ƜF$\У /MΔCƸrR2)Kӓj~E xY&Cy&G qޔÌ1g9&fR}Br$7DM;Ƹ"toAN(f S1/Q)i_;84-LᵅG΀{#Gd)f8rhffΜ9/D9.z2J bgX̜>>wˍJ3iKII{tNU* A5ga Aĉ&/{hM3Lژ59lw<D+ w}w#=/^F~ Sל.OeXVmjzbhqd{? "9G*N0Ȗ 6lJ=ē+r\dϹ;xlbqA͝۷!G;0ǭf.\(92DIKkT^AN"@ tiUx㎍+gZ+2uK$ON9) A&Ҟsg.qVK0,fJ<~t9R 2h!E f`k05}=+FFM}ƭ5-TeRhm\u^o ۰qu$Im+yҷ-?v%Ty<>>~H󘰎 h&# 2:0nk?r :grN Ȃ8XB0s@9yy>_pB/+\;'O'Cý};vg{F-(d4h q FVWӸjE pvv'˛@[,J/L\m" ˵ʴBswcCά\" &.]X'Xyɥ;L1?xؙ';-1:u !ޔczòWmmEݯ-['iv;jWU՛v9iZm;Gd@F<̟'*3qe k[Tdox IEDbYI#d͸fw޼=WŔ, d𓯉zY<]]GD$F#]6lSp\{PzlƜS4T| 16-%Wl恛aۧIyXC.^qw߹nӆ%Klؼ1(x@ RӚ;77=mSŀ{(|M1!p0; /utu[63/M vC|J&X#3DN |?Akъ=A#C+jLyzh`5jI-ٴ+װo6I{kΎ::0"Da}/P޴d "Cz%{vl@>;zh=j tV1Op "@Ǚ?6'{Ao=]/J+:rٜ0A( %I$H!g.sֵM"g8/ Ҷ]]N\@./-.":@qEFXwg![!U[VZԜi:}~t]]1cBYr:V`ͅӜ1kl]Z`1J2 Kɸ5H=% K`j<[~聻6m^5a!8QI-_zOLNǩb\XKz#]t%K{˅R+-U檩J2.N3gӻ`Rw[8@+23XHv(1{= k;2Bp8>zx_K\x#kEVhd Αq Hk"BgȈ_9FJ^* \}×gh"M'l^Xd]w2o`Qr5*tt /^vxde5ԅLM/u|12"SZ;GlG3/ erA Zisjz|iPk(QsQo6\0}IdfRieYS&FO*3-]l k63d6+xcBsɢV (|ʑ{|7iL .fi[=_#,k_}};8}'sW_fNr>Ё__Zib>[ ISv^Lڶ-X@@Xh}SW?n4yN'$!XPj+E>lgP:1hstXsuކlqrlbb:ǙPJ3ngy =cg.LϟUn~ͦc8GIqlшfAu691U ӧ g^[@ /&-r:mE*ܸqM\zi+[(֮[7/| AEhxuV^"G^]i,n-:aنnX΂ЊVZvز+=ةS'>C) l&)%*8f`Dg-gE\.#勹?4luwwrM޶@wtXHLD@J b+HXHjm+{uhIS7 \6ACG8aeAٳ"+eJ]agZogksg.;s^|?U)^j:vwtQӜ<5YUns/S[zGGp4 캮}M:9arAIP`(JT+dɟ-dKlY$"LD sꜻ+t~TO8\[ug^+MDPHb}ggƦ%P*i9yJTjc(eQL?qh>iomYzJyzj1xTolfcG zD*5:tP-ZtaT:&`,l% ($)%vƔrKu޾E\v×_~-8ǟz_ړO>yرw:}}߲TȶjZh90Hi|rKyoZsc<)ќ ,a"jmTl ]ʶ[sUWWtvi#)FA4a\HE(# UL(u;z `z^8`'9G!H q4> 7~:2] QDѱS-ex3/ʤ@._f,hN9#p⋺?&Oz!`sݥyp4!1pmAtg*C`Vk}P87S|uϞBB`bU؉SEz4ۙbѲ"PhuA"8+JJ?7 Fmss%ۖmA+ZoACc*|Uեe f;t.Z,՝kqF-U+a\'q$g~n=-".MO_{oʾ3.[ֻI :GlZ 5j/dF8*sAc73 惟稴X`H&_]\qvD@$Ej H RB2Z= n$5[0ioP #x|<$M|;zAL[bS/osԄ12BnZ[55D04<FXIZU mZ1%%fFBNƇf>hee3sG)zi(s5c¯֯_{*5ϩA`a%h:a'` )һ?׀uXSZF_EG"B4p )o6VQom}9pk_zڥRa2@Ff>ńM0HHFpԱU*<5h,./9._d@D׭_6;7H8\&:@ &S@0],xD#^TpM ?B3^GөvMe2K)$g',_9}\m^6Fa#BMR|v#MV~Ǿ͋c3lϽ6ol Y,Jx`Z5T2O^-RIK8jtTZf411Ffg&3Te#Z[Ү륒 3gh620s`: gF/|lW|˭SU2ʪE|Kl5&T: 8,X|mVWCC ˙{=]ittWakiii@LRN"&eO:};wYݸqYo=~ٳ7nܰ[m/W8>z`]$~՝Ońpr~֠$0k/[r9p5W4a *)1g{ % h@`"@LdB/봴K#ht!b)0r]N.bT  fRJٿON~#'w2)@ J gF13ClζDʶ#ۀh\ RJ1gyc<5ʀAiel K8㘁aB4"> "rޒ˗v-;68[rkݽa:3OTOlGٳ3tr--Z#:p4<СDMg;T* ʕ]h ĶmX$c/ Me5?F̴iLBnۉ|-B Z˕zX*6d(q=>sQTZҒ/:۬4˶dܔsH/9%O8 5pF-D/W\yU" A4 '7nPqDro129zoU% !$Ha ?@7gpH@"ѓCgo~-zs?hE2V}ᱱ$pdzQŠz]Vp>7?x1"št__WgO/*m(j!Xőբ8)l6NsBL%JyXΗ*hx*aiԃ|`C~lllvv T#@Ho\ h.Öζϙ)F0 N='a/Y|=jeUn}gU+s?iڗH6F3r`c*J bYz׷F;e-[,|8BpIJ" R\ kRYxҼ~$IPs륒X+WRsũޮN_mxdusPI!y/r!^E؏6[(oyę/i뽜gYm]m<@#̏*+ߐEX(-t كg9ӹJ>] ݰ^ڮW7Mzmk+x"Ubc3scӳ%k-]ԩώO6H$Jbyۜ)C8$Vygnի/l $i%;;mTب48qqy[ y;i+FWo\=9"ɖ [©'0[X1952K;_Ju]mC~֛oyǝػ^x{=ssŹ٩G},+YرçN,[4')Kzi׾} 1zժW~rt^!@ mBRuN[K{!le v 0MF9c3h$vw_cFy>S(ʀa-`u6E~#x7uzdf;W,YHgP(D ,p%VG14f40‹^A$BH\h/.lslWW4g2-aY a`ٽ_I4w۟]R eB0By=^I \;^)EBj~VYE)=uvZֱ8ѩSaGQyN: h*a<ІIP+0JXƦD\ y*\E׫-7 j:1:D=7m*-jQ--m;.3)/b-F53 oɉ RJq0"#j-qtЩ=p(Z1y=hmFߏn۽}yNR !<].p-Zho8Gq{W RZ)%h#@p-N8]Ǿ`8|KN?V.#1=6:: 2)E UZ)L1n #PrfYɤ kCS===2Jh\=zlBd[[: Z,[<+d NUu?J1ZC-9I~Pf۫3>rh(%Am&j3ސlيO~=xHeD5|hOyWzڏ9zԉ˶n)陙{޾> )A)Bm1pr.nnYg $p p An/@K6L:a ȑcCo sjFY$B+QD:JYH2?S5 xOLPLbl.8]h9x]u|>?==}nKXJ9RJ+B@n4@e"CdiJ*aY2)eQB R9ߴuX#h68;=lk. irV~s+Ӌܚ/n!uv:,R7DL%iԈF*~`КkiI ڶ(7h&''Ke˒I1(L23'S+V^*M9ztllbr֞hYtْgΜTtuuqZΎhv۶GvzEpλX|Ν;&&\f2]qfm}ݝm=:o}ڸiMgg'^}e_6^~UW]~W8-Q82;qzLllrk(kF(]duɢ؞N)H @SV\Bs$`P"JGɌ37v| N BMp=a@0D=[ -~޴>-];o k01FÙz"eJ&" ƿPr`gOͧ^sra Jֶ2v G h…%х?ug 2 7R ܏{L(3#to_?3!3[ZZ8V&o~ڳdK5$-6p4?ho r|r/ GvCOwtY{g^9~J+E ul;X hlL<D4_ւJY2 j;43%=Dm9yG[ܶ mnS[[2E D ?cQZ+ MKkwG`ǎmK Dﺌ2bs!؏b ic(ѻñ-ϳ)O8+uwic= u2h4j s-o'y{%#?ӫ֮ܳ}b #98唋FP˿a@--<ۡT0ZNۺJ Y@sQ}ɑɁ8WYR*mTW_7Zu4bsۜ>(7D:xT,B0fnzG`zlOGOTL$Q 0Xzd]S1_7>X(\}Hݶ՚3ZdUoϢc7js drfgrP(GhLCMY:4P?iBJj\ `ot2 øT,K!L+}GEçOV7}9r^hT'Ozy}ܸqc q'fm۾?E'GF/[:d2#GW*Ֆ;òh"V*\>yeJ}k-/_51=O G,n3(bDyKzvlؖϮ\%MJ2cS+!IJ,Fц0j jXk[ABBsM_fကoZ 0 6ܡz];nݾk-[MKp`ԦgbNg)7ќ[@l|PqhJkE?18_mbd*YXP)F9IJR$zjuom_aDqz¨kf|ڦj<>^}G~c3 (U0<:l[%+S9(35Qf;8]m&l Da캞lbc4*UTj)DJZj6`&ϼk2j0J-.DuVPb8eLӔ0't>mʲdё~믻ӭ֦6υ$8i4?m{Q9o̒Q(ƀ1cF`rlGKře˖/]0AP #vXDU-svmm[fho?!DZVQJ <'(UZtUٹ*XE[SLdJ/բ:r4h(Ћ)Xx>nnc&o~xhpxL&" 3Flt+d2-[Mȟ}hg8l=MOuN 4dhpR֒$ ! r Vr` :ln+rˆU\Φ!PoZwJ)Iʫ`o֧>~C~'S'O>rJƲ*Nt |0t=H6K\!\4 nR hkiutbKGTQ*f6ׂ/L{[cGH(as?*)M&T5 +'m/N$ ꞡK=zdE/Zri6hkkIl+S̢g>kRKޱꚚ6@PBx#ٖS#\3-r-XٱIF|k/ OK8" ZZ3y5Wt 4m@+xooCh%Ԉ!(,Kic fwʀZ|@ߒ6Q1h~ܷa.|+ssEIm;mZ ?FC΃d t( #éMЎN/A&`#'SmԂcǏS0x骕ql.ݹK'3O?svh7w<ۿ?QBcLQ K,ٴyG>E(bC\$߾.h<7`05v":XfuhWWl)M .V,_16:O''&_|Eef;VP .SB#C U=Є!(-ҐJ{px#=='\6gnD}jgϮZb˯HgRF"a,&R\=}%@#A A%[::W^WNg1 Kvm^#x-)ʕ $LX4*J-)o]XX&עq[ZNw:2M4 $D@R z*HOMzrM߽=@U;XUs'hILDҾl`bzkH$ҕʼV_-GBX~3N 5í݄wÍ׮߰Вm+sss_~W_^*' #nn_6nЇK$=\iT J|d'޳>;AMJRcI%qjrr`>KV C{+"\i%bL5&-/9ڶqXKAP'\pBhӞB# Ao^-  T*VE\@ A`, " doN>ȃYndqfu|%}kW4|2FLzG潤Eg;ek@j*zN@Ԥ@0l}mc>GPpzTѠc A?`A+(c!Ysz 롩LXkf6hnc'F%(E&F%/{{>[7nREJΧӠ8 j[zC)y"EQ2ŀquTG&&]סAhYv[v\LqFFZa6W}m3V)P#y /.9KB(ej,Ktk, ekXgILJj8GCWwS/9r{hD@ 4(<ݞxT-=9_GQyiAѩ_;:[2hߛґ7i_Zi64G,$pOOg2Eˏ2vP[~B k\Omh-/wruH8bqO};wfyw͚GA22);#)E/򛿸骍u˺mXbޥݩ/lDZk Erqmc jUgO]q}]`X Gp%4.Q>S TGO2jb-׶D* aFMҔ0 (ejS%%1ʀ8Ճ4&zZz2R 8n|4^{ٽO}乘N$Nd'@nݻ_{@;W-H|uh`\q=/+Rryvf[7ǟgvkƦȈi={/~Ekg/ ҉,HRkCA,qN0D/}v))}~BˑډF 4$2}͗޼jRdXz\k"!e)-#8J&aQ Dkbh&Ӏcš'# =~ˏkŮ۷6=r`C/y~úEb[nnii7FikQARhmY%!,2J YXEs."-nپlEŝlfj̩!le(`HMdk/K$I.V.sӆRJn;rzv#:w-m7l٫GV#jjq\ |ORkt"XښAks޿9LE_RYʢDv GON d\©wqF,VwuFmV2RFBp;FTo4fgg=y7-f>ڒ۰~MKkf߾/|2ܺu㚵ˇGNܹ˷mly FǞܗ?uOS? {;o*qhm[w]wOL= HRixtlVDB(UwQQpղoVJ R5W1@BQ1AI)5ZYnp&iht!s@  %daLin0Hp^N9t4D聴C;_>Gw3b/^Zi@JSBfFs"Q۱lC@6(fBhZLМ CB# ʠE;5=[ PʄՔn+Dkc8?bv}?tI#|OX(\d-OJ̩#U_//PKR}@_Ϳܮ~kQOǢ6` *%aǎxg[b?7n\-;\q$3 ؑD~הs`T[~P+Emn+ͧrk_ZKZ/jF7BT3 Rs5qy d|` X㳍g^xu0_9uz|Skݳsa__XTe}JcZDQJ !45i o]P>1&@ٙK"c3˧9s?4BpQRHtdq#(*3;>זul󯔇C9YM ~W:> _o=uؾ&^TnRIw"ԟ_ߙ_ 0Y`M* !+O(ŭ$g}…%mtP4z<_lk g-h2eSJřL2,W\PH^|nnkZ<%v7j{_O$S61FZ h0rږ{wuwLNNh֬]%b8uNhJ̶XP;%` *ӛ8WnY{Վ:}F֪IGX| yT|Ae e23oɌ8S@ЯBp@b$,p-+ eBX(iM瓙{w=W-0EJM~厖,7}Pzn"~xQNvڶTԫDcju Yb5nلY@ 6\#Js(c.@@ş P*ćz`bEa`$݋v9!L~<.# c)c)1 c"Z@)G^~W@sB8X̒J m]wNt.=|}}CÔbxOݳvêᱡu)PH$&L(!?Gҹ3$j\t-Q֬lkm0F#|xdaY(,7B%I"6TV,;:ϲ@$ B"JjXШU*5Te42F-9b j`A0 O9i:K8/|޵YGp|'Rj/GwĠy睿~ ] v|#{ WRM8}pA~CL:zh\{׮|nhpdhp6_[>'B{Qg>ݾv*}w.pY`-=7KG (06hmw Hdz_=;yxpVu,"  DQXQh~ ̲@re.E\ FPb),^Og(L%˗/V+6ۼyeZg2x4jBxG4\| 8ڞӿ^&-?!J"kd mU.ፈm /;6/pڈ53T[ߥxӑ fW5;R`yxkڱr@2ieAͯ*-Mc'Q6FӸ.#" r>(f[SIH9r^TG(  #l#b4lG˥8::;[ mJs髮b˶{>p߯/NṄ?T*Ϗx?_'>񉞾#k֭җh4WqG g>sSg*~A3(3 1IY6׊z Q| \YEY#A"cMZRN!&&Uq/D z[NHSwCk,D}O~L&ߒݷt-n&f?;+k .J+hoiURڎ %m&y"iz!0bs%o=D5ԌdjI)0ԑZM\/x_h4E.0l\'mzuz<!Ke8v(2:!/NߟlɶwcdMBlU!@h.RH`MR*0TR*c1ZKRJ PLLbi(p"IK*YhyѨ (Ł ͷ6l3$ 7iBFݲ%o gmmZΖj+G~;d& LEs_ܧM !Տ9K [Vp=[+QҊiBt}tsAH@Ssmn C#3,B) b)1[%."̠F0 xV8,aTRqH CY%F9#;{]ﻓ(C4R*cqI~q1,5o,&l[]͗0GR#"# f1g_S]l=7/\Ǿh4ʨALVo(f9t>0fq[E( FLZTnnXG1Ph)daM"FJPPh(6/5@~[mݼV%Dصo=(0W S?39:|1VgٿoTxqd|t`w`#Ch{7ܒt=#h9o 3Lpux  "\eCewoK0˚}r|:ڽ{HK뇇ώ'OW!B M(!0#Hj8s9dSaWXeZ?}啃u0شi5ϞnkoY~@/ݜ quljы ThcAY@|.HAԨ7jK!D"%RNdnX)M.[<33WήZ+tbH:9M FѪ#|ypjlcEζLTffd7.#LqוU7 l0b0Ʊ-~yӖ^VǵYJS^֯EBvbr\. XZ[C6" ÔDUL2r@$bI `ZZr-jZX`븜S!podjɒ]Q+W4lrvN޽)R`rujT sbt/ٰzEoW\v hPʍnREu|c䂛r39 <DʛsFFv^z_ܻbW_xuj|nWnl}L[ュoJ04ͱO&BPbPrε1RmLs,eB̂G^8m'ff/=sz뇂rQµ&J6  w}+B`Gg3 `/6D"HƄQZP)e6"=(hZ}ŪeVv |S E5Wo'< &@?wޫ~jfS7v[עd*%6ǁ!H83 !4h"lθH0aU*eń4t*8V2_<<_C "|©#FH S8޶w2FH]f cLDJKLJ) L0BҺ9/RDRQFD֛Aȹ Hj 3RefjfÖU7H7ٌ`@TbrZ5ͼ7YqVPl=}=-v ""~˭~/J3&ah&&f-+ӻQذ,Me\(~B_'I5M[$(_LۮwMs~`q2F)7 RPFF*nkoMeRƫ7-ٰ.Ti*FR PAkApc J@6˚cHʜ;~(DiX62*2ɣNw8_TJ 9B2aЛʹР&hVRi:*f+ӳ%nbl8-aF]֫Ѵ3)ϼ®̜M v0FB ŹbT+VKfgU?t>.O)y]wuOibjߋ?u,~;nW Z/׿7=>?]). !nIcǵ׀AAH5Ȁvi:{w3#2 H;?A}^}Os ~޻0;_xfxxvr! 2s^(Q@mΊMkz.}gw9<4ϝ=uy㍃JF#BXG0F$5#60rA3FQ@5 1x+W,:um\cT@K#d3F~b Æmqa c "X2 ኁt/#"qTCPJV.h4XS*ɌGZߺeq[zJľWK: 狾JwyGm-+.[}ۊ RG)􈵪oѵ6Xx3OUNWWxrC F0u60TZql+O:1+,HڶDB$<׎biY%D鸷-]UW_λo 뇇&zVwΝ*-_h/|ׂ S̋/ʗ}ȇ?4446nO?z(J$nEB s|fIe2T(TpfPƹ6H)BQJ(~Q&Ӟe'ڞ@ K…KboA0;Tk><7{Tlv,p Q_9xHQkrJeشB@p-oݣhPF8q,vj_z 2T$O(mpm՞JvMݨ*)\/:p?RRJ(a).?PJ< wO8==S/MVVbMkfd4OFWxX5U!A!ܤ$ !0R%LG$RARYADbA 6J64{ۯ\urA!ؑS;_~-0]\ugoȤ  Q\~m/2W,N@ Xo{]wcUobG2&;bݯ?ه#غ~?ϗ,YsOQ`B,l8^6gp! (SR PTVi%l^w˵wV֦+.F=׋w~?t㭓=('b\ҷZւ*SVH(@1@t$v P(`5`C>TR 뮽">˦D64l FjAJlT0ZR\1q, C (%j"6ϻW܇Hir<__Ub!e);jkOw=0ʶ 3t =7?ٽӿv wRiW^r/ޣ"h)QQbcs!H4S\im-X'h%0ՠ"3J^fT*_ѱT+e0TKgNJZ lKөd*螽{'+b#PD[Z*Pzu7tˤ~uUmG6B`ЊcM,80ӕ)cL*:32N% (G_xFY(- B]ۉdL DbF4TKx( %re52~()%bӇ|@)m;b\aٹloD7w}@#yF#AX{Oٽe{z;jW3rs}ݴфKGFzAc؈ΘR m0՞{5wa /ƞ7FG֮ZN@op;o?z򈕰v7 ".53vC 5p7&97s)/~E&P&RJy]:b;ncͪ$2Loܬm?sQwuuű J nPc#] :$@L{wWkk纙tFB,ᄡ,Ε;:,ΌOOƍC9/ULAVXgGS^NKV4q Q-K/@X|FƄu8J4W`V2ZfMЀPP7ݼo= 軛rl[<'$`ŏ7m=-m}{zw>gws^̉GQ|-K.}'Ύ˿c'Vջ]yPZ(͕Z EIJQ tLGaǽMoHa (4)B^* 1H}UK!_+WT߰NԾ}8"dDDzO A6-O*  "%l4?u4}/9hԻ;wl2|wsWo˯zՃk֮t-AD0(ghaԶ@, 4  Mx2\ǖZkeNT2]:DuZ*0~CBbjGTJ&!ή%!sI^fۉGp c`ioڕC^*%4Ruw|!g8*\>VJ.03;ͥ ʙ3-vhpjjӷe놮\>w˖mٲef-y׮яoή^%uםJ'O}j57pKv=#7l_'{LϕuPI9FqF-@+mٴn]&t.sƦ!Yh4FPTiiς @%0 F5;S  @sIE=/{gT߿o3}!V*S aV%\"K!9EeyM'hT}צ4&$nͻegޱh L  5\Ħ1JqK0J4G_/kl&xE J=+EaBk}7&fQ&ĠcAP*&SeD+L'GᏴ̸L$=˲8c\~CFD"!*Ζ^R =3>>25=A@KN V(Ƒ md2vgWk:cDzyvmK\_y7^5{zZkqYi}?æH)qp-k7^uյZ6XU<עK?XO@%@cE2Ԉp-W^s58xF:80ݕ$6]AM0%na],@@]0zsbVb]t}_Z:jEUu 5RIϣ\:B )1Բ< ju)Qڱ\*;qfQ2nut,[*F#f U<_.vV+g5$!" 1fV5;{={;v(ep|4Xo|G `O)ꃧO +Zf,:3S 򢸡LPejײzٙZ412ͯ|䑉k:78>˷obAM9M&-}V^#=Q\~'=?ϻv=O~GLr}tsCv۲eˏ8igsHmow޶ " RJ@ 2P*"堑K.T cU۲ |wΎnǶx|\Cla4P'\X kh2J1 ʜ:3``lvng}X,'/}o/Wn7ڧ|.¶v¨њYTXSl0h1asJp 4F`s%ORFl_$WXYb,d>memqZ( 3FǶ\[Jh_jcOj\.M>0NlA Ζ>&[N>sipln-T繩dV&'m[hqHo4Z-y866K.[j}8cَkÇ-_>zŋ~c W|dԋ/ ͎sp KbhQT02Hm^e+.qme42C7f4%P" ̴@m-Kl(F((5\8}$6I`D\3Ctc=~qƭ'OݶmWn)6] *Ym܋!tRx'8Hy @`qe8y'~D@NS㠂(\)D@OYMsD0DXp=̏1[, 0 a 4Adax.mzUu>K]UA 5aJ7` (k,=h?&򇏞-dz2_Bb)h:niv=oHӮcFcժU}kzzQ'IdZ9V&6 10 1 p1 CY[W{GOٹ:dKilۦ(eJ׶(ahh0`Jk,TJL(+S:rH>YX_> XW^ٽ;"9q\>w ]S|]9q,)R*N_ H$GOwdmҊPg8x a[&h::|ld(LNLtw#SIT sa|/x( N؂` @ p{S h 5MJlԂ;`ӂK ӷZS Jċ~.]'g,"c csVV(/sI0_z@KJJFΙe"-6X8gBV ueٓs='&ϞhЁKZeVb&Ɔuk؉|\@~W~}cɒ?~ؙb\aԦVlڎdԈCSJҹ`:Q1whls~TetJy]%vtp ˱~Wn.O~ON8+,7,_S~84K˖,ΫgG>/'lX/ۼi5[ooU9p{/@(~aْes3z]#`Mv'20J͐M\ƅMM C|"g}`AgIԄ2mZ+JyB0Vawm=}-s/]&h?`(!0u4"nYJkJvrm J#c"ZkXQ!BMKso-( iEiD-4"pj[O6BMWZ{#?<6㛈EOYD4u3;d-3%@ PO !z39rZkJ1HŊjlom nB@( L=%,LP\,emmjTJZ9ԦZ]q:.Z58AM0;::!RI*d{ssEJY..,/ʼnt#璙l*-_tڕ-0LJJ&(ZiJ/*ޛHJQRk z<2~ӠX_w_*\c]|>/DRFd||';njb=ʋ.FmhK^Eo!V X "42 !KB?yu7[Èo%z8(!,Mss(RVY׫Q8ubp57|ҥˢ(. *R\R˯džZ- °@?99'DрHrʋ;68㰫5cّ&9lD*8"iZhc.Xm-L+éݻ_]rի:;:ffu6 ,wv7d2{^hx޾-W^R:éG-"(6g2 5H#;seL&\# m4fﬞ' F jͧRn":Zm%qhy B: \8j F$@!(PqJcsA9_;x-.][ٸ}#Hp0up8( R,:] J^v0zs#Mfs^ JqSc'4bc@b7{Qia-Kڭ;֬ڸL 0y?v.z0Jy!0˺9_|?1 &+;n7<’E~j׮JèB slU lB1RZ  -U`*O(6aUʨT1%H 6i^"H(AN d:c9LV,SdXת l2Qo4(ÐrcqQ ܬuoiiUkD;drjvڍ4=Z<;;M(RF:|WgbT VFDy+J`5{o<394kGտGlX"FZ . ,𣆑uBҜ \/r7EO̅~q!߾0p?@iz^&߫ON$d.ɄF/6?{^U- ?;6SH|/c) qIIuwBJ?:') n:6xz ȱ-aX(򕚯մwG*J pQ9?nbvV~7|GŁ{;[?6\W{0$(\xۖ /0ݨHUt%]xݶ_w<p^zᥝϼ021kv޿gɄ(~+_-7ޖoh#c>Gj`P]%(~W7 0 ۿ FF48v-]\o?&J|\7=48L$zɩY@G gOdijK_c޽TP.xQhY{e /Q_V&ʳ/}CgO=uرmΝ/n|MF )bf{<*W2 KB5Lh3H.)$~YJI@@BUJ鑡|^^hyѧmt#SqKc C(B[ٿB F6#YD.۲@h40ț oϻ7ÅI,DSC @ЂԑED\~c:.-R6P!DgN{z0"\EXlI Xqrhى͛>UJ'O͗⺩HZҒL9c;^Q-:nrt:˶JR6^;v;n4:{}#NQ!:ǡeMzmFXenkkǧkE$uQ@''YUL,V le6,Zl:4CvP`LlT72 I0:vprn[GKgmp;vruzcrjrCBH }k na[!Wg{W,W]v@ǀRdž+5Di?Mv@hƀ͛6.[_։c'k{mgySI2_آEoMοDH)J1E/9o$~aXm  `5o0fM>Іl+$4&"ۨٙw֖ɩɱbVC7p\=DkP??[x-n\+M|7ٰҙt"(1|I#QL7:9/]V˕eH$ RjD庞%L̵Lt}]6QJ}|ݖlq@[R·$F=K׭[%bqI:t<y۴wXCA(/Uu-늤ŭr%=j"c2^Q(-a+eױ]1ZSF ~xaT彯QkK-udr Q7jlʲDKa٨y,sOӄPD|B?ǡ45e (g2m}v&bνs^ !dJih4H&iF# upN($[` HTC溷g \|ꇲ.ŎT]i855UFLֲ@rӧN{G0ԒqRPfsV)Vg;qĉ0HQsTVA5y HM[L0cv==a떮!E]l@%)R(_W{cXEjF6'MּHg>O/LL&AȀLjKij*ƁRB(Z1Jyvfı/~**˥}{R*0m<|JUV;f73 I&83r5e䮭$jV?;ө7SӟF'ݪTLxt=[ aBMK9h~:x,:3)A' eB$B[v(%VlbOwE1Cag ?}[=>0\pfp~A{REp#k}~Ja(J R L o}岡-1c"JMvGดEQP %Idb cc\gmm'~csmo[_ȋO7*A'PK+khya\*IJX![XpǜPż('O)Aea tye#YJ8f.߿w_Ggg#s+UJ +K!jmW @kC'9 * _{^36M>yb}du]k2#ɦ RՎP 5D$3/;qɚS-LNkE%Mfx| 'fZaͶQ3kzg.. R"B1ӈO''..#)%X,U$ 0m0Bc !J (=O,H< !E V4FJ4ꡝC5w_=hfo2isϾɤZS'N>ӎwřLl.sӍ7߿u*7cTk+~-'N N"0 {{{Y?8UIny3f؈i[J y*V,IZ c]t*-A;+B铋cdnjqmW2f Vc!DV+ɼN٪@ F!ln6[2dTZƉX\Cߗ` ;eYNmy-Ug 8>0s( Zk-84H (V5(Q;Fssf3y^I&^uq;#דNY,oԪ&-₝ڽ?3?󚝠C az*fRLR(LO̭#o|/BYk%P+aZ۶HG>={v_yՎ?:;?*39Y,+5ۿ  D~3 kmS0*Ng5o}ZJE^^X~g>O'?kZV: DT.o-?˿//n۱>W@ =Rzc]@en=_P @A7LFZu+oBb6ul>nܶX 7`1Hغo>tC&Z-/-[͹9! 0;;+]>G*PT@PmarߗZ)Ϝ^:uGy8rGeNMN^yv7j}Lֵ{$! ZcRG4(ej5;#[|.3>:;͛Qo6()Uo_O6K锛U{c;cr{Q*lQj6[Q6Ϟ;=2265Q+׉9F(|'(PA L(~&Lv+v$Dd˥h@#1Ur#Fx|>u@RG#-}(PE.Yemy+W)R/'&4m=R]y8gNnW$7X6955;e&+gOd:UDˋs0X7~! ЈRa@jim^ؓ _k &Ru\ v) i oky=|njhdAc@u@&Hw?K)1{TaCJ* @(H/f<_qlqK% sUWإ{FAto@Nլ 0c-M( VwceE`Jla:jb:7v!dD?fֱcl^4m0۶rH6MM|n5g[e Zke0+ʻv&E*BTahvT2MrHxA4hM0XT+mH,7u+BaacFEpA AI0FI]uPZ T%Qm,=yʽW Onlb#" &zq̕n`$ z鿷jGw٥ w|@]l=4?X~az@ku#5`D 1X[ q'!(E ja%e" !tWBJ.d'$H@X^m|7/>p{3\8llm$ݓ[,L_V@Qon2ÑjE0iy@`y`FѣϿخU5mQ± P #MR+L BI Ae /_zמc[rezi%ZSs`b[X[c[QΌ?m>~ IIfgWW4aC=ne LcR 0B kվfY)jr۶leY=}a>|xnf^6f ~ w Dlp%-L ]T1Vg?ן|n?=}fkG Wq`J M!uIHБdO]?m;&@FiDLzv;=~jz~jJ-41r3s3>ӧQxqG?Vϝt\uSj7H<8 }VݿO* @\ $ P<޻T`O r~7n[)˶j^Y'W˫O|*-۳{wNfل'KKǓJj48ZF߶Fz^+qIq\8k0G@b  $㗍@kF]R9H$PE2c:;í>`Isv8ylt,"2B~yiCDI0bf|o_kqN imz7824rz`wm&>ZZy΢\Ha,TV"PlfhOk2l6kfmwR.8Ƅ1%1@|?)W6 #Z=NIM7ϝ>1[/ b`昉bY6rJmiq9WpS)4QF!0ea((apo]y N9mSye-6AO_&tGGJ BD*aY&()# җhLSQD(J f<6|뗠ok8SZ$#p^m}D.k2SiRb4hK%R'8v{|b)=ҒeC)FJI)t:J30r덷k~L'ӳ3 @c_yew\;BZYjvKE@էx5צ "Js~܁CG C7{/BHr ݣ'NW:{ ԠO:L#r‹L0njLRN-9뛴Ryzqiqnu_segcAuݱ >i@7_wO~ߛ^g A +2_Ϝ>s˶ZѾk -oi{>VWTB>2u|3~RxADe~e] B"W8LR[WR\EdW0v7 0&2 5Š5f 02# [T>399EF?[[ZJ{o~ua7^{PpSё{w$*W̰˕JY/.,./zcchVy+%'?Fp-(B&B1D*5T!g^xzP̷ۭRt5WHo)aJKl`ؤHAz R>W=ϳmCIGAB1H%g`N3m/W1s嗖gƇ1Aa/-tO}sG1lc$md`5$FFnl&;1>@E qFa!aCWEB0Vq OGƄ$FJR'BFæh @RbB\ Hk9gO,'ը<96˯}c%}5ʹx/]y$0֗LDdp~q 1s\;C}C0(215X D,C)֛ɡյ͍F݌0MTqir("] :åok6!)H[[}~BrE`ti.%B5mCKL\H|cz iYD Zr AhysLq``t`ócB+oJNݨ7s٬iYq7*F1]('l4T:>ήolfR󧗙g$OZB 4#l:zWoյ|1N%R8J\W-x8 Jk zyi=+zeLf̊8}DzV[LDo;:Vj5{rE9O4PpwbitfO=+ZYP"K2q,iL&cD< ){t}@ )2~hr(NBy t]?( ˴RvM[o_}OIDҕR&VR*)JbmQC/,mٱ5[p AaZipFJ*h>WHxQZ沶rRijaG0M/im,KEMLmo/ϴN;x.򍴑+Ll{~S xeyRvʼn9QjkRݮv'f H"8N!0J)! R!`)h JUCa"3 FTT"<5G~*l~__w׭ 7z/U$(XoRکO:Z,t5<(SلrCmv+qPazZ[ݨQdb 1(RRY0p,O7ʆHYRJeWWk햓:Ւ=ݽwכzal$O~ر^7u\}g4hMvjrrnZ#D BJ# $rxffbW/TORCؽܔt001c}_yV; LF:T/oDwF#B1OBe56Pa|gԶZY DkFŘ/ B+@)A .BF%VZufz)C[fpԶ-#۷M Y :v'B)%yH(eA)رZ^MYCXSOrgfV VcT*=Ϧm/B(J!%8 ĀoEP @ F&|ӐӸʽ_me(]38w͢Aq.g{^`l78AX[R^qT'Q87pԩ3gb5yO=;P9[ܨpSGy9cڂ ,J0;H|nn>qөJYnki!8 Dl+e0 !-k%  A6$<k6jR(0Ps3svJ%j So7VR2If0"R*"4rB^no2.EA{CG|ЗS)lN4bQ)QI8N`&\4 UW ym߶zYeyP)`*,#"yZ"85(V;{dyH 5h Z+Z B)gǑf̖AtrlPiBR4@,e^n*Q^Y-NO=Qܲmbx+$pC>s yx(O9?ϨQl)[A>o6>`ıY 0Iκס#' aH*V"oB6 "A ) &~k}e}ueP$GJ`0DWl>#txWuquz?[lÚ؍_| 6Hw:8JWf; d} ۳{ώt&sمE`P$L|'F]?kvDXuB 7o__pGCzVFH)ϞoIJ76jL-}aZ??wu=wz-Ց*$f&?>|H57_//ҁs=~QI8!'3Dʥ'hUk珌\{- h5qEҝJ6?6wɣׂulY(!HD}~e}cL@JetfLMN޻k2J"P%<ƈ&1f$|t%Nnj{ 'O%RPFUbA P0@zc1?@!P-n:P{5Jaf2;A'dҊKŘm۵۴R" #YɍNolRRPLf|ԧ4H%!L+ҁcgkP&`$c9L.vzmc}E5)ݥJXW8$RJ-HTOOԟOeNA&@Bpw_Ef~A4FAXmPlrm<ӫ唙韬up7(*H [fѨc?b"ݷI߯_ʟYG7@bI026B1O}sJBpJ)R2J8Fr{NQ?z$!ۭV^U28[XZKj?~fݹ\q}Vo4h24 i.IҩlDt_{H1FXѱ-4 F'Zfó 8eT?؃͊ IX~@g7~zz=\.qj-HP;P*iORfaJ$%-[rhͮ|衯4fnٳgȵ_u=w յ>Ͼ]o;O(Ze%hB:elGO?y$T_kojnaę\Ж۾#M_2muUF@@^~{? t?kn,T{QбS``1fi)Sm|l#/|h;1r]{8gO?[==_/Cx?h&Џ&շ d7fRJB r|h.eK`Ƕ7jPve ML Y&Eq &AkW0 b驱3{vO.,V7Puێ(ND#! uan͚Js0W*i6cwo򣣃LAki9c!QwDP:IHY.v- k qSfJC?ԱtH<31&,ulz{rW]G!!e@e\hRZK! 3'G`)  Z&ӗ~w;qB`B䂏0?~fNL? o|j htqX@v66NQmävU_u_+@ ,2;}nfctW*F$ĘRJ0.Ro{{b&lOy{Z@oj70=l0$׿DSy Z.E-eIJ<5 Xn6*0He<+.72kj+U#@`\&}=2*!Ͱ)YKjZ)fbP.o,Ԫ뵍J}Gz hJY &I|q& 1A9e#Ŝ$aX(S#'ֳv>6c*I65eci$ q\n[Y6@sB{&1\S2f\$e,lY(0)z}I=c경[W*(V+lЦɒ . rMZ] Lijw'.#lO .خmF73Z+%С@& QRr!~1J*Pxs]L;R2'!X* 9@&#Ta8cm;qaF懤ҝvy0r2VG(eR@({#R6"N1l"PwyTrQVkW:O|řSn6A[mD_S#477+++xttI$jϽf-c(Ũc[&#*;~?f=3A'Š-᷿m kM O~1ek;q;o$YOΞU j[dݱ{ff`S{foz͝զ[ʗf,u&7?ݔKu\: X+) "s͵W/fLOhl..Λ]}63͌81( $׀'Kv~0X۽=}~+ťVE\p;<4S҃_ -o|{“O={9HNel&l5mZi׌tY7M#D;vzٳ*Y+u-k+r#'&\~õǔё^|_\kjlx 5JC/>c/w=w~-+uN8tBovM@))9}u15f#xai0w~KXV5 *dS\6V\@͖FdR񣸈fS0=/[ w\$  뮸rĖ=ih.ŇHH 4]uŝ$1JX6- fRYN[N,cB #)5FhsO?|\IDM! KRtףf3|!/ {k`c0B"j33ŕȁC'=fi돂9,ײ+Mw|k͜;u W{m?񅅅mJEjb9%B(c e[н0Ĕ$BhM60[.o|F%FFk>CG>vJ*9?=sj۵uezӞ.,.\}7{H* DM0-529c=?b.s]wZuwU7?/fSX LZL;^/g,3j$e9\P"p!8*IPڠZqA0فޔ<Նu#++ˍfFмͽyNh^kNPJcet|hubN^T:6p_ooy@Rj ,lzvO̩sq+,,p?89³󎉄 ncۦذL3 \;0ٗ\V5)uP 1ǘV*U7 Vj`La.,,ol2lJ'>~Rmd:SPШ7?yM|efߵ{jh 0Рa 4өi1֎혦)t:zqq\zVݬ@}WiAu,:ikF͹\[q^nzzzr!BZkEbBHp  Rb%3F\hhiZm54_YZH RJ$|@I) L [Ədiqe\(z)b"ԠeKͥ].VZ Dar|D JkL(g?}?/Tj$@$%BE-'^8.3-N6cem.-6 þ([\]/kB|h`{ JIl|?<~*`XYެ/ڽ;WOM RS'@H|Ox 1Á , 0re}m= 5z}W^}׽؏9ٗ}YgXї/> =r?R:j5667f@'#Bɫ'?p @aO9z̹wяD'!QM=AjPVjrIłZrݯ O"RI,///.-A̘3;=!C`SdT2T8g-2lVKm!,n:c=>}33Ϝ#0ZZ(LC'ntueJ/Z?g}eZʣhr;s040MZެV[m:. 0H<+9%&ioo>ss)%&R@ }]{nllٙ={Lϖ}r}_S Q$ 5vB G2MS#@Z Z?ꂴ Yn?f`쩓FH( XƆM-e" G8RJ#dec2N/q9,GgϜ6 Z(a°&0bdwh--mV֗ΨrsvCCf;wry \{޹eccc|gGG'30U'c9Q "pQ il;frH;E90THj0ae2)Up3X9B`1AeYrmu~san!V!4/l n$ϻNQfڶJ(5A㱱aHY 1Ad PJqRɨ 9ߗǕJ).UmN'qIb;Ea"H%hV#?Ҧ D#v\y(%kF8|#biǶtѰ̒fN̟9vvv|b$IGa{F'Ba`h㇛Fy7 [[T>bʧrZ\DA˜<|/~F@6:mbyܨ7B?TIe{[8uL*nıR #+ 46(ƶo̬ή~=#1( UWElV%!&0cr˸c[:aڛ9xM R=յr2I#S/7;K1 `EZ'x/Oxav{Y$}#[J`:Z0IԮ]{?{rV*! T^Y[Z _?ԊjkS7|x Nָ㵷o_[xG=۝>"N~Oav* ۓP.d[;\?._<ϺbD/.6O<]y_wҨ;_ m2S#%"iPXnx<^'I$ 4Blr+lˢJI. jhk Z\.;nY^[^<z˧zK半cP,V#1dfc muW lX]Z :ب0qtR}ϝ~}vlG]Hw%C/~K$τi$FF]B__NAkVn7ҥ|E#u}AQW۩'~x}/.,4@6{F:u٨0 Τ3}QC;N!Xm?k k'fܜ=Zߜ3)ˆSƱRiP A($EQ^u+RjT﷫ zH1ffS`i]o؉20zx#qIƆ''N#A B1ffV3cE>S(q\# znwK(l&IT[k+@seflƷ rR uSR*X)"p?N!J NVSh1P(c0g{f\Y[n{nus+%7-JBs 8I8M(H)!tib$"H$a8N5LfHB8zhqnsEwb|j,_(tO"A3-'Bд\aq"9WBr!a0m@Iێ+X6-0 µ͍L>:mB'<"s)Oyšnp0ukr^֛ZႛOWk bLa ZAE66T)_;r[ZQ480H6SʧzoIwle2JD>C)r+cO];wcNkP##??|u'%dzS >yJ v,[noCfj `%2QΩm/MR}Ԏ|Lˡ~_O?7NQ`>rŜG H!)U@>A@xK>|Ȗ|*MB胏~-4Yܘ4v HG>rD2CF#_,ݟ&P~xtq"Q9N̘+t 7~~ryai~ymy쩳JgǔUɉ8_u $2-P7 軞$/h@ bO|~՜.Z(?n:ٶiPJ@k FAx7h2QI,8WQĽ"iyk%BuA!_4 cjV*jujxÕFбa&XzH}+a+sv!c1* =ىq˪f֐B/.50F]E PXIx1Pp% :;QLր h):&cÓ[LJ'YfZKJ 2RB43LMc)8 X̴(1%\@H]tP罉&@@wa@^[0+++zm@S'UW^;7|ȩ[w2K/<AD1Bkх}55j`\ Ob8FHiZn,/elouKKkw>lIdS} ɤ 篬T6 PAeV[.;{!ՠHZ <( Jbh`TkᤕJxH B5큁tM0eQc |>vr XpiYf SzpD63MIH ǙL0,.Ujw_@W*5׵ 1JabL›*BP,f^oB."Y\\Ĕd2F5}b~e~Lz !Bq<0Mg3,Vf3!TJE0!҄QӴ8瀐R`XR"Bh0-Y)6˾_wC3;\~ڎqض-dsJ^)mc H()^#0fFgRN:#66ԃaM {oK_Da5;]ھkkhooTkb7['`d|ui+6Qe2<1z ks`ZYi47864y>gN&C;Ji8 ۍkUuU$H \B")f39jf!TcG?s=}NHRR!h (Mĺǟ&F{ 5i@`)+ 'MFI\i5))wW\={}'}o{_6_n{߾ >0:h7=yt 肓Px1{r؁C[;vMjk 9` %RR)FJs%b-BX!P$0A ( 9I8LxQSn:uGmOIdx`$"?AZi륁DZ\D3Jm˰B>#Rn߳wZɹVCL@0$ȴ@nMya XcLBR'ibS\wWaP̓N@#Ji!!sv\#m1vnuR&Š  D+bWM2K.4c /lfy߽}-( zSmm) %k—8&J)?J 0§?'?s 4lc%H&oƍR:m#,RZsg˛v37;JN`vTQ,ۨ7|JqE翏WWUxDZ(% JoMt!B pro';H)J 5Ĉ+m5L.qlu8Z]bP2NDd0X#$1:N\g V)j ݁dSG+ d݂c6+5'S1)Vr BS/kVڎaDʱ][Du+D .)T66O9~ פcP3NLǢmR-atl65vDqOO1)p.9R$t*JL@aojkV[qӬO Z=s΅E7<4(X[_7+bGZ0̈́'NLeEqUJ+.G\Ar_ *PAN{( :'8ɤhTǎ`JII8JPFeHFq ; /- [G'Wk'^:}iMpiob6'ky~NI'q^TJ:8cvaJ qp. i0NsôR#=-dYϞ=O=v Xz/5ֈ{zŹ+2CY?N !mne\ѓ*,͝9yxc\ش1)f\yqiǦՇt`S<-Ƞ(]GZDbI+0v1J@ c:Bŀ9`P2Bj@s+3 tPol>#2lƊkAδow7?'|o#'=96($mZ~jy}a='ZI$h aܛ9q 0(BN=eY@ tzZ5I 7PƉgϞ@.,E"B!B;|Ԏѱ|O?"![(PH֞zWB {aH޾mΝ΅a,toۛ%D]S??znnqqs"`% ( ?]ʥ3z͋RgeM00mLwN^]I "@kEr59=@,-x#O.7!Drı?6y7=X!A(\=Օ~Kem1_z[ޜ)iZ-!u:u +nv`eW_ 16g9mo8?2 ʮ  飧_˭Vkd+vfFg>6)qEPB@kP 2Rs/_̺|:l5S`XEН<\Ca^#QT *_}\qk)׿w5[w^ @r$'02(esRAJ)@!D0hKt@ tQJX룧OSϿĈ*Dj-4H zߞc#GW4ym@i޳Qcaq! #=OP"DR Be(-!`h-1B@(` Z]}(eZ"ws3'N̷>%8mXZDp Eo>{دՔh'M8R ŏi b< [[o+9}8HiJbL4ΜlkF_[N߱L? R) Nc1MW֪Iw`y 26Qlۏc01if~xCNyi<>ڿs6${{1uvLð(־Zr)zm0 1I4F ykT&Cdm!$QĀ)3bĉ\jB Ni2cFӮ5R_J+ӶzzKI$1-cb&k# ϔqrmMGFmtzKllAH xemͶ쾾>BrmlXfNXT  㣌FҺHP.Vqqfԉә[KB.B2dDf $IFS*=08B\jS4lb}QPd 3)7掝2P2Q~ЖLb6WJ٣V:ql!!}#OcP)BZ@Zw_مm[~<5JT$B1Dlr<~rozyvRi#e.eA A,7:ւF+Ջ_v 閑+]i R?Tk4$hYI[Ĕcl`EcHD"teago޾N[:|| 0) bA'rKc[@%PoՖiQl8O7{촽ؓ l-op@$F@k]kjB,8c0E A)vMy禧x䡸ێ II3ϝQ>i8NtS)y;ޱm~f'?wPVtOM\;m0y X7`q_JQSn @Rs3Q2bQRN:[*mBAjR3S'}*ؿx5?=0h&?3OOMlٞtBW][(v Ӷ,!aa`L`t/G0hVR)N0c1%qz/뷣#b?+eeHm6e*Vl>5LQF q,/lˊP ٿ=̿:(J#@k0zFoa_،@V a}MnzW~ltI;)6@2J@p h `3#&˰/7d77ׂNTh؄W' ~~=wa=4T:hvZ[v^y;m8z([oMK5;3=}nko= ^KwshaV̩fPH FrmiF8_Jel+!8d@OWu 5ڝ%1'&|?VZ.u]DzY,9=rޢe:Q *g8?8}0NoomwD._LӠuNjxe2|.Ņpi 2BTܺm`4ڮz+^:pzsttԞbPV=d뮿1mZ֚} WN+DMӤA0?L)$q)mbFN;7wĵŵJPkw&e-g fn&JD p``0Xp&\0Ӏf+00X3C~/oZ'plztuD"پ{op lFZv3afld2Bq(a5w{Ξ) T؊X, (V:q@ZibͅMTig(aPSDL`%z""BGخ \>uTwgPx_#1`ZI#a&IBG,B Z[Q׎cVD_O*^HvӭZx/l{C?pwNL$PxSOtH)B2Q_{mWv һel~<я|bvq!%Zc @$Nc/<1YYhTO֘`2LSN48ެm-;;\smg^8rQ3o)Wlt+'ONKՍ{m>`h@ֲV>*G@%.Kjall|mi)[|)zc.|O -R)PL0 Ġ@X~[R)uJknnO7oƧ>z\dW7XR3vomo|o+CcY;tDZΦS%@DD ~-. W^8l;?cZF0ϛtX)BT@HP$;F@|'lz _BcrgH8J qWϿ P@G J",i bmFb?Xx;\m6= :VsOfb㧞y6S[FM`AC>?_Pװ$yVǜ ȥ]ȸh/B0 }oڞ篭1ƘaRB %K==\6SJhJG^fJ{خSfp!w BO˥8 J@ bG3@!J$X3Ph4PWt@w~zbkd{(mAKHԨЏ٣'b/da  y3B0%Vbb1a H#(<_,ku_Jzt`hU` \&ki( :60XAHoyyIOo9X, )PH]QJ .T@C!_}0)!1Dk0y_% @Q\ӈn!&"Hk- H!1&j 8N?OrUW89iF#r@L18]gkw^{sc,B P=9TW G ɀ:3us(;rʥJ7.&΍*|c?8>~xU@`L_zݧ!ZhYQM,ǎU}ADWw)MO.&UJBm|yةKsyaѩQ2KO4Mlf<:tp޵7_H}?c͹[&|?n -?rk@:nhAwބ!`HXMoغkmخ`4j7#EAK5F3d44R&+vwaX\xjCK)O鉉oO$NiwHU*U(l+2f6hV՛KKKFGG(:v4\juo289q>}}8)ohn(16t' .Ye˶_şL퍂1Wn_9vuSKM ,R15GN:y:WXKmRn~Z.>v[nλbܘbc'}D5L4e=DvzZKAB͝8xQ޲Fײ/wܼ03m50f~߮p(LcЎ>aVߺi~=Yq72YVƄKt̑BX$m@xuoX0<[t؛k7T:=8o}u7mF-c>#ffR\1WJʛNG42S^'fK#2JJ[*DY5Կf[7_476܇&D۶4[Qx\Dk6k'%\ -Ўų1RJ, (@3W.x(qwo;}hf 5RI 9X1m"Fl P` @޾}롃N/v^+;+z&nE7ϏeEM(cDADHZ;u䚭3誱vu\50vlCZ\z+_ %`1D@ Lg M !6fyvᛏ|}޴c(Gƨ &gyV޼i}oVC}lJXPZa2ppl,ұ  PvmfVۙبy[b ?qⴛ{ qVj2bwvX(L+UZ*OMϥөbOoO秦RPHs8l gp`P @hv͎k%Z֩Sgҩ`&n4jJ!!dqq9t6cYV(8K&C7ZtG_\lo3gɮҘ;5^\D^z\6f5QH9-A/U*ﻮgYv.Wf;ww2Q #eԼ\[fkduaQW[7a *YMpۮ-q,v5%^T^_ƙGkKDc c kmC$a:u@("jSF eV Jx=o>L13f< FB!\̜Di 4jD: ԢKbtm"a(FH6!́S8F+ #}0 -0n:Zj pQ8qnĉ'X56!ОA~i[Jg 2sÃ?[ۯ 9'thh^#՟}:vi@P@GQ^V(T2P/U_~ie=~Mw쟛Y7:4~|P}JFeߥDPōN?dR('|ԮZKe @XpƈcvfZ%V~Kf"dsD([@6SdW˾,W7v=^"l6;c1.,q8FJRKʉN{\S#H8""%N0N-Sv WW=+?H`ARK@ۯ/-W1ض* AB:gth^1S BDθC3=|}_lRqjbdL)u-KЪJnWeS_$X)lz'b.dRBڶD&&a,xa w\|q7f]ް:E]r;GF)h+lz[ݼemW8sv.W9uBwOq u>?C}}~%?n6ʍU׼7z )twAٙ ٷ!'R`;ȞBя4qr{J#]9 oIlevj90OkUa~tEE ұqh)H<21UtdQ28Wv+3۶EA܎u縞K}?lbfP z!t!?P-!X&o4});L2.rBx6jܰ," TjK)dh6D1s蓃zyX;JBi5 9}m5malWEsr},.RiVy6 j2n0vJ3(U.F׬ ;&JT-?wJGSSsb1dJ??78_f$K垞AWhV83%vD"eTΟ8 RRFs܎o~ ݽ}Ξnبg% ")9jVQ#<11~䌍-*(Ex5i-1A %Tk08. V;vXf_ffH[&Fd[Mp_ƁMg.&mIoX~Ԭy ŝ/2?=ӨUnrj#Lh@!lLK-Çt2S VFE O:~npdkڵ'Ɩ{ C Fd|e40yDŽvVX.B%ê`nXX--?W}򉧉[lv㶅݇uG?'4  ũS'4m`B͐&&FJR 8_/4jdTez{ڞ5dUK n;=y~bb걑b+S68nr~$~`ƍZSn[+nZ W}Ovlg=w=0zʋywoW^;7[8 \̔x:`@TZ)l^x-7wEL:D'fbxlH* h0A#MZ[utY^ 6[˃;~eRfo M\H D~8;9sp}S&Sncv±I'/ZƐN;^bOwfBX!fon[&82NmH!tyw=wjj%#F8\(##=]Eov 4BCP`v%Uh .,Kp86HM @(c!R#-.VA%Ӟ 8w[ x фPrC&D^zd`$UHS? c[\Z/Fskp @,[m'ufihK*FB LCt+\$@r tıN%3a&&!|ŝ<5A 1nt#p7lzŞ;kեނpZ.rY*":az{Iwu 5c$e&J1^6!NtZ@xia˔lk S΅ke3Cz=F_484 ‡4@ H~0*@nfM쫄z[-FֳvNgJeU.1Fq%g疻n=~ض:`"/$0\j[ ,DP8@ZRՒe:̦+6%{:9?aۈ A^Zk(V6480<8WrB䉓OlZ@W_l8u˒VdT'˧Bat Z&_za E2 `;8Un?uzW\n:Po/O~/ ,:;=b~oP640ala PڡJAO޷m` iѹ LNEŇXo#c kv\?aCG^<] sO,-WjHBRaTޮ[7A6 firz|b/>W_qx>;*/{sggg7l4^?<\aUۭfZhQ@Z?W&aDqLyl1qow̗>Z" ;wfoWnQIBÏPCHۼi#rEŰY@(_ȏuZua_ $b6~W~~MKyםw`z[-t:$!Dò^hc gdMeCt^{d31i\'$; V  ض@XT+JYv([9_0B(H j˜`Bք`.j+B#@!DQgjDk0 ΁FQ UhY6%4ά۰* E5uR@8p1 /dw/JtPW? J"6ZhZ+W|[n}`,7^7 ƀ:0rKj9YQiT`y$,[{zwyru~[?zju[ѹ7D"+B#61m;t_׹6A>QupC)4HR¶3R~W7|Y~\*/ϵ@H4y<}w;ǽ-ejfR]5TIɤs>sLH}S''_9`!EmhO=h uH(:]T$cusK{hGKT[ijhZI80"]Z_UhZe=iUcIϥ )Ri&B?PJ07eh'< 0 `P9~ةڍmNWox |٨R)/I7(\'c}mYT)*cv\k htYd<*nXiKTg'n{=]~ԖPjL (S'Ida~ҙ\q]/*?~T:Bml+[Bo>MJt.YTPBJ̢AIw(J1!D0+dR--49@ϳѵ̔k痧Յy@9u VJ~lFmn{CbK PEFP"&A z Ɗ}׬F4B,;JZΕoԃu^f7܄j5b{ AA0yh`}GWq! {lh[n?8f.qz˺-J$ݽW]miӻk}kz}u }''5%,QkשAE`O-J1M+#Џ qv(Z- Cl21>U*U2^ àUhG7td 0`,#Վ:s@-B=˻kF}?;_/Zݹwo=jkit~8p(q?}MeFݯ;HĄQqHjW~xۭ߼Mcϝ|h3dĸ~!Og?{٣W]i׬DB-}}=@œҒMpӍ\3קΞ}"%(6fZiD />>kWԑ}>{FK?`1F!RL>q5^uaqrNz[o劣/%J659[wBL:a4FE` 68]bgq?F ]a8K@%/N[|~2&DC(! @!hAŠB'GH^7S졮.x0 ZRI%̒4ѳzֳ(& Cc1p\͸~_~ Aː۫)ZQ|:ՕMݮҞW^j*lF-Õ ȋ%%eZ-f TwfsqM2e֥r 7@Jך_+zTabz  dWg>E6۷a5RSSBM(`1 /RAB-I n*uib0e굠\s Z{܉)JxT7Srӯ "5BQk=;;l6r<%`P)S!b"pBb  aE4ڡtq\͗榌4(滯~77* -k5ڜ"g±goJťFWWKTB:}yfyjCwSCkwCVHՄJg&ffgfsٳqSR 'QPF2K!g 4DX#}~[SNVR՛r}Ϝ^~gN=s3^U(dF žǏ:svdtlb|6R 5\8Thhaf##}Pڽ~FKMyY03XX-અ$&2ah AZc (].7~OU j gy ZZ}]Z~lܼG݃Gfn*ؽ𢡊>5~~zv~a=aGqeն__^cM2l{>?\k{ah8j04(4$^džF7OϏX!}۱ l@0vLv>>^7V٘Nf^9 _70< qMϦ!@}/?7'j%=Fs]]?C?}U#k6o޾ʿ܋;w;rXalaPNQqv; Ruؘ> G#Qq %*ʹ) :j6'Oݺae {SOR,ϗ3'ϠԫFW}>| P&&LDM^ 7%ڰ"+ej!v5882O ;!ɄK5Fp PfcSԫƶ\q$X:Y3@ !J+!FiLߝHZ e2ڌK=GKlRB`^m.xgaۼǏ8F3DŽR"U^QܕqB0`owGTEs񕉄!{d%$vſm/C&F%b]opՕr"U7bhR7WJfV clZKW "JRFP:SR#6H @R±$qv& 0T-x'~?l06X3;yφ Y{pwr+3=^=]¥j]bvtMOj%d ;"ApW^xqO#/tZ^q!ʶ=ǮBL\ C e8ϹE1XFTtl4@l/aMUo+)ѝ!`fmq+6JRQR,tL69Tjt6kQr%۔e+ X)PFaO̢bI7[f2?Tf5W0. ιHfh4Zv݄2L Alqd2bU+W@sLlT#ϔ_|pqyyzjI%[\k[03!ZJBɶ\!,ƈҲm;I0Fen/<& Ҋ2a4a凇G^_-?=175[)jt:PB5p]jWo615}DPX;:f Z9^"mgx )kKY[\- FIDKWREӠ hqW*Uk帞ItqKLD|=뮹 Q[ܲ:&${q A$#*΀Rxlϲモ"]]_Z[P1u#7g,ha0 AXjm&ؓLq0 ;n^?g,VVe湽'#iB 7_{͏~[Vo^b:s:~dca1uPc:7+v1Vf0{n._rL>o#8jm }J&i33uJW1ɦ&6Z{{XG ܱF %KbI$I-` Z-PD>b۶m=:CD£@`Vpf }Q\9[/ 9ǵ73D"tT[6F ya%8wQ.U@3SSOUی4q=ɮ|= \_1L8J͆%Iڡioi[ /ATV5éNO, 0: jJ0* rG^9;9Y^ZPEQjkL'܈ -4tlpV) mmqq~|b0(c(`P (߶ncW8>9sRuY>D Did4́ӽKn+ xo:X*PQGv3ڬtVB A DaĔ 0įT_ڵ];[bY-ÀUͶVp;:rD>-333-`EƷ d$\J P}{ dg&uyRiQзzVsFE2Y(?ACxm3Gb)< qmOͶk!`EɤouuW]{~3 Xb m)a2sM$\k ]{=x)pl5܉q =o>߶v\7lZ $paӫ׮8r^8q `@x{ FQ{N'LF|QZ3NAX ۟w _o۲ᩧnRXx<u܌Z+m~:TKKϞ:={wdxq7DہhנF @YtriɳYOW45yT4 ƞ@s0':@RB;eNŠq~YfΛ݋K"&ةb[WڌӤJvV 6MA0`aJD 8RG8SwD۶ec80-? fls&gW"`1%Ĺ]UCV4mWlN' l4P닅n`T}JS3pSɴz$^u+O!||vB^1%GZ3tL9J!A-i&@^*/_'"v厴tj{D@E11 ER33`.!h("2 cT6k gJ3)&v[>EASErˣJ8Bn lY`rnzfnFe,Fi~6_$đ֒R(a9`Wltbaz:@4B!lM;u~ LVFC%$hHY "c_ZWZ5>ms# tEd&50u%!@T 禧=cTJVж(52 tW**/37ޜR|5ns]OӱlXs'(e"[*`!(2`JϿvyR\_5Jj uc=CmCmʹWm'SWq4>z7/<Ѓ_~a۔ssy$F)| (CMJx8v+~Թ(T!S-:N`SplDŽoL/= Kf??A0iB  8v# P` ;5Woߴ~m>kVe¸ٔQьc_'!QTH5d3Lt TOMN8s giF8zw nXF.$0^W>:6}EFK`6焀AAo:q컉}-A4-F&S`_zr#W1 Zƭf3 Pk_|\7BI@YhLgh.(NAHB7Ȱ%|45wKa L1{|ڻ+ηήZ? (Ao&fF5jK*]Mz)0@ j 2sq!n""*'z^ޥ[$;F#umZ|`@ov8 =2::8mM@V7Jc]]%1:2&@:~S3Ck.{+(eAM_ׁ LpW\RD$w|d\Vnh)^zi+Ϟ_\t /|}6 , @K#%uřgT2h86:<KQpFn2R;TDBcG_?mVG96(Q˸( ch( Ͼ!|I庺v+e3aQD""j ad[’6.a*eKDYєuH@9wtϹ]}u}n6RZ~(Uh)5EPF(BA& Ù6:Hkׯ1a:)dqñ(2bCCI$\,ewwOӴ0rnYZW6~ۭZEP‘޴vЖ#[am~l|˱%e[фRRC mMcq۱ljVSs彄6D@ň:q0nèM OIΧ9 2(@C-$ QĴM,e8Q .g1Rjق"ZEA*T6^/-Wk=]ye RlR %NLNN.2bB b? zy69v*ھ/v4W4,@ /m c,5>ycN-J /AAY/G4*tub?KX~큃~{:4:\-Û7n,/zoO=cOX}ȑwNF %2m|꙽qlمEI~ݭۑF`ٳO?uƣuWmf}݌r)HJ3gkztI/UJUAM-ʘ$5AN4a#凿 hIԄ&:uCTkPs =ȑi sT wzh4 ͨk8/MΕq(wkK3,Ģ3˿p&^&UQvwԙF$%*QZKRcyנT8:]Zl@R̦uo㎻$\_ W,at_J3 ˸iƻhFrD(RF3`?d⌽X(Q$Je_ĹxD >sVo(>-'9K&Pcs'ImĕwP~`rBBRsJ%mOVܳ7"]NPD9Tuudߙ.N/]8սI\pA raeS5epˆF~'jn8T)+O{~zn2/?ן#Ռ06RaPiӦ5W,ͽ޷p J& O={jlln9{v"_H y7,Q])kh;Pke.1оw$GV:%p 7ʽYFR*,u%@`NY&c.Zb4,.IΨ.h0;nzw矧ݳs˾ ,6ꊭkbp$?2V سWp^"U웝kmVɐ9z.21@-M m "ˢgrMWzU4μ:ǟt(3B0VN:38<0~h)GBVAij2% Zt A#j BRn2(J8Qu4@ 8T0F sXw1ӿjإ"e%b%)zzVQJRԢ~41J< y1Bh,%ls&&F#Zr=?3h@bHٶM qD/Q 'e35#8DlWʁ?~艽2QRHCD|'餗׭ꖉK 78i'm0B)6Hp-K9e$ s 3sQqqc_-1Q`-BX[5H@3 U##j\ g2scq!eZ֚Ie$P8xX>kː6$Vz9j=K;eƬ[m[R6E4 Nc `'\B Q(-J""ù@,%OO0Ս <̓`/D"i^G'\4G@îx'{vJ2*fz69Ã#ָN"_,$\wn㦍ۮr7UajFy\rm7o-tMvjG~e׾-؏@ PJs3/kғgѓg)u;]t AKN0a#A$pb~SgO}Ui|I:& 6مdA`f&qhhZNL]lq*,n;L!e@A!ؐV 8~ܓ\o}|yYVkWD(l>vn!-7ob< c4fht8sMܲ}mC_־#5821q)%:H{b˖HԕWo`f(>ڊ!4beWx(z$gJ5&us3QkyT4\wmw )[HixKBc J-ҖW b "U+,P!EQI!H(#N[6J4^2/vߊb% TRd8P& scnۖrݔ{jı'p>e0u#%k9ˋŮӹen )5CMinT<;qh^0!Ȱ8cm@s$(JhQUP[~0 .v%Yl3jYӨ$usvviyQ[FXťzfkVY-tP*D*DXqL.#H3P p,@uL:M̶d6431~2*7J~#$:[Q`9Du䙑EFRǚ. AjvT;x P )Dqa(50ʪՆLV36`̎ [;unb\-7KK\`N\|ǽoƛ(xҭ ۝3ԙӵr)]̹'}ЀB:bNQ6+P$@)R !1hƅVQ@ڑuDxp˺,n[Q*U?3?l&D8kzGs[^ߺRdv89A}vr G@:mV:} %ZR_[Q/u) E1N.@{~_pYfMW]qc;n["0:ٜ8rdq~> B[XR˲$Ҋ3m]+GC/hv@3h#Rzg^¾˭ǎ6'sMP@a Ŭ¶c̑* 7tÙ=' ]޾\__1ctM&]ϳ14!h4^8ۗ.e~>xYk+ʗ/jM@"֚s{s?} <n*8ugcja =|OCM KA%w qfz<7~?L6zo{l>x[>rҝwAJC?{nfy~3BҜc֒1c6m[u]r d3\i6BntSY1 knAlJm#; e/>q&M$8@ 15nM:4Z9p/7ıc4ӀRW^wmתm9#:n9!` fkGGKYW$Tg{RJD!E(L1B. , 9𸣤"ʰ:?ݖV;rtݦMpbeytX_oQ\Y՚ TԱAKVՅrjԳnM$VU"joC"Z688n-Jr¶^ݳġ!j%$I81ƩTRWZڳϋwQ^3  ]入RihLz GC5Q17= ZeM\ XS-XK `jF `hb^.{]w\}un&Ip[0 Vlv3k8 O(,(7 p?&j'N@8|K hFkK%c֚3N8aCKh6"%<e$$v2x 00ra408ԨAatyP΋}u'~j@>!poƛnPL2X,{R@@xe"-sBH$+])ͺM~~Èt~l^ڭ&e\#) RLMS庮jTTrL,$ _ȯퟚ8L: FQ0 ]}ٝڻ~e%Nn65L9y0H_$e (³/oKܜ)C`#V_hD*`D!'h:GN<HʀDQ>T:w>󳪭!GlB* oۖ(dClsǎl*+vB"?42vl||Z? c0-Z ONj-P=|ڛ([|@HЄ畧^鷎>7xͣ~ŲX\ea2DAx$ɣ h҉ZWZS 59z)[@>H֎y]s@'tU}jEH2ޱWgzdPj ,M2qf6tI/wػ?[オwog֡|._.;\XhU*\V~׼;'l%y,FE  ;|闉cM(Cma ۢtm7_q8 S2JZɤgY|ѣG}ߴi 8e!ųDKFH.`EB? 7T~kmpqԗ!Drf!$C0szS̋fj܉uX~anjw}~"WO?f7c)mFvX36r)ו ` F07ؕ)lٰ~d S %@4GOo=viar#M Ӓ%[6oRG`\O2' iƠ|daB@$&zWu[qld\K.VEn4C#12BpJ~Jrnn!7ЕvFi݆XLt/(, c/Hvp@JvT$5%7nrS]v$(FjB Q-/6~,ssɩ`:J^7}եjH$N&76ܗ͍M_}KO?  BoPydHZt*!e/t207q>>Z-/WM- PF A R81hmrS'sA0q]o_bɱѱFz0l"HDYH/D!]6 xLjTh*[Q[jB|juU.b2 8q6[v?^:|I[Bg_7`b(4 0%iA'g'-.=DkE`@Zr˒F>tɶ*b%+wZMdX\Xl6ɉ3׿X.H%Ref3b~{"ZL$p(fA!6R ^兮`ɥ{r2Tѭ=4=P dB2Z")eV&veLѐQaRQTlpqy˵^ݕKg1@$K:RFգϿs?,S"MbHF,J-JHr^~eȕF?⊘xqLZ(Ѝ{go@ 4bV-b}מv_]mX׷zotyTk(Pm7w=e}'UFon]E|xtM`7:Zu!Me6*ukӮ秕Zn C] Ft謉"E)#ȵc>b%+p+"HԸa[?s* Hpu~ѣs3&%6lZeipv͖d1 lxݽɷMv嶍B`Pd:^9ēOwRz޻RL^eR @N?C}z3[#x"۶=zu^u7 A-:. )T l / Z#3 h+mVW. ANnZu,m(hը(T*Ԏ2K3ssRw&{ͷ4#9TZn]6[k641"#J%A\.+<[mADNk̞;'VLXC#6`nQ,dR9{W*e BwƍvjCCST.]מ~3].>߸qC+{ׂk}/>|36sXOP{hZ׈5!MO/?swedIIJ baEFWB[?ȗIeF}ê|!WfWa5-4 R@fFC ̀!I[ /cMwDBZ #N2X~Ӻ5k0x'}Mckz{|N@P@.8ɿH"J `8U({P.<}_{8R0RckU_j-/c h8}o!饓W"a9JyıW}*LK# nIvzŴmΛo̤RNLN"+j()֖`ѡ[oqhRc Dhakpi<*8J@DPEWF0kby .ŗ~?i H-M" d(7F1ʔ -ak_#$7j4IDkYܖ}+6MmtƶuWI/L\7RvxS4l`۬ժ??,ۮW_pڑT&1>n՚Q'"фS  5P+|ım/To`n߲=Sl%I7+z)%'(AbXj-_\h -tBV:"j4 O"$$UZ} g}' :*R\ۍ#Ieht3GpBKSGa-[]gbbK$lb*a\-TjL*fgQ0V^\(K+یb R r[Jeq;ch( !m @,bp*hY!F1F4J:6-?' fҦMCÃTrX_WoԨ/.OO*V`6Ș@JY~]/1PҠqHU Z" DӴ=~Mht]7}{Ъj·'>٬CDرcgdzɜ Bc$@tx:Ԫ9kVU$T[@c88LFc:Ϡ1( P{=7ҤB3]A7$/P̯Yʫ$@]""EFd7fr)^ҿm lכ5 2Zr 2 e7†%rlh! ui/T7?]廮yH{/PB:zN+2 _,? @*5JX NRid:aFDqWԡݯvs!y #(Ksssdvo,f }#C(4׮+fi.WfϦh$w~zvSL&w=o-<43>^pHv,nt vQjCm pN]4#(r{& D6v4]_^Z VcLAeXb\5:xW# QfC2<"&(FSm X*%12MPuh b) o  :@EӓOwuoǵWZ5)滢,<=j‘! d{lsGrmODu˄H5"2J9ogz2YD'Qq=o7;;r`ىZ}K_/.e+8=g #TN dP. 1F)F)ͅm٠5;NG lzշݰzێ$8̝ B^'} #N&a̲ͷ_?~]R% h0 H0@Ţ8D4r`̲VE\wwͪߚ_\:|t]kIWX~]|%ZO̔i395\᭸SO9}~g۸(pc'>=q-&m7n$[c@ h`h:{wPۭvfx;+o>m믈ch_%5EC:+Ll3F ( Y^xO[+vҋDi-k7'$Q&1J \!ꁬ+4Loo157Qږ˄5s|={0o-|H<}homQvҶ y|a!&ª6KKD+ɀ[K0R+ˬ6`((Mzg@y$VfMoh4j Ku@Wn^SLx }f ]9|8`h<7}EGlaU[F=J-A8{\eq.n5v$Ri˲T@)/+&N@)}=9 rAd t8|==ֆE vQ ްfÆuke+Fڤcj* U;_Zfq%,2=Ǒ1`. {6+k$?;γ-_9}ziԻ.^L!B 7!MBnHH $!!tc6U%2>-ǙdcH$Gc|u^0u`U81JBtTp4; ֟+}/{UH2LTi%6o^j k.YT)0R`٪5K?FVBC,@lUUu\RS1@LX6.i/GTZ&7&He@_SGGv1 * 7)&D_Gv5O+Z1jhjJ@}ZKdʈ"TH,[xn-Om7{X.$+RMLM Ӄ%c>Ja~X,E #2.BR/ʑ5W-XdǾ}gB1cւtZ]2U!XKJu !`Qp0ZjʌƠX-OHZю# Nd sLG˺ZZ4AZ (\gUjU,sDL&&0% 3iRD/ Mw9v\!/:qrz@@ 4yoiMp41Fbaajj8 uT: C]=xXjmjm5v5["Wo7ŊWQ*q9ʾ"<c@YOL5 Zy:!J4:9121h97a*\ Ùb޲LEQ)"W6Ny޵__$e>iY8CȄIz|!"ʌ)x|ܞc=ZDHsMZ;nymM-ͅ|q݃$j5^&bqT'T~@KfMYbկM:nkivgKaJA*R޾١iP.Yh}ɑjI'0׾WnPF`#a1JDuv,wOlϽoT״Y]5jHXAˉA8"{8OQ-ٺ- -*YJc2>sdfT0#w~cLa "Y"v%# 0/W :|&VZni8'I/[V:"᜖ A Zp<޽$X>g1Q_W`B,FRltk20c&नLO /Xx|b%OL۲m}.Ww:vO31'+ ݜkʀSdzU 9B PDGtL_;qÖo;p|;>|NJ+Wt |\.UgfNe0GjS!wqg1j."KJ/Yt3 GMznBNjo|m)@fRk֬8*NʥoӦ uKCB 8v~DBHF9 g 8^sZƘAB"C!_[|G S ScP[E\$yfӝ`|X` cdm!ʋ{v9>۷Jm/_>n\pJW;sfhhplr*]JF[cMhcm{1{鷿-tjh鷿h)gScɠVf=,jl\jCy%R$d R֚%0` LpO>wlA`(&.ի/' ctRdrfzKu uoF<5<2698ZO!ڢxm"@l]k#mY;DdP 1m:*a'Kr1 ׯ%zՕA75;>n"9%?"WĒq"" !¸,PX8gH,hKG+eA3 91PavbFO%-]ڳnUESlFzF[%\a@HUdqv`讃Sm  Ƃa\`I&Ӝgmm7\uq6l.\*;Gg,J$3/# !`mvݚk홑\8k,1`m5N@xjf-uSHGxo~B 9on|llq`k6hHHv3;8?Md/^hqgkA 3)xuN+ GnjY-\Qy䉧~zL!&ysSSwgp F6)p ǟxG{[4=5K/_uλ9tx: #sY &;O?~b!ъ^uWl^ٸxUZk]BFOfm9G롂o|fҞeuƖ͗n_}Dq1%F.q#h_ș>]=4I.ped{C0Ѐ匣Т6?t|xd;~*c%$ZW'׵6NOE' 4[~l||zj+Y_9xh_}cn͗^qQzZs#z1t^?ez8zPkjٔ!אK'C |y Kh 1l w>{nx:@&4ẵ?w]RoT!ny^}c[|`77Q\qy}@MLL#KQرCc@mZgf^,VeZ~]v.n y["CZYc5_@"?Q&25U#ZDl᳅oٳc < yؚɨܝh[nu-c@2ɖպ\.XPio^w_U{ŕrP#08"d4h91ik$1h](XU.ZĺR*k4j4e> ;wgf8\*MJA '"XuB戌L}K឵,hc ˁ!`*\"MLr t;q\K%ElB!2FWlٲPT8@XÖEمwÃR#h}JԹ:yޢ[oQfD),Mn슶mohzG'' #uִ4GdYE.& Fm^d5IP cc2A3q E\&YJHf'& | X˥ @ D`5X"2 z|bյk>Xep~w:GK.;lO]-Mǎ-IP@lJ9"":[\td_v\h9 zU`$='遆DSR,ػbXऍY#FǙ3 XK:k3"ǧ'&˅SG!VMe IP1c= ׮XupHO}VCCP͆%R\n C@z)V_;_8ZpElzlwݳaafZqgNl7nL|P+M0X.YaU|kϒEww M;}ǟ.NN%i,ٿl~Xt< `y}-y9X: ["T$0a,L=y6&SԔɱo5+oLJ<<3 ?+'&o5F0T– + þHGB]HEKApHTq:!-PU_|֎FH 8ڒsO`9+k_x^mM2/iULđ$aD5?Z'„>N}7n7ldWZ2{__p}HIsgg?s@l<;PՕnW{{hc ;4X#m_ͷ{ڡ…/ _?2N=~-Xch#;6&j >9r_TK#XZ|mlt(B<K/Vh OT#KQdCֶY<gɉ uUYݵxi{уTVbx#p?m'P̭ТLb<|j7O188=:b.96 "!92&9g羗3ɒ#@qj6z]8>6?<>3Iqtl,j=.t|+e)FL+JR|gڵ+ReB2&(^ 㠅3rP^$?8BvԲDDsϢ…{X[Sk9( ' _ҷ:ɬ+}0NGf?ߺ\&gA^x)l>Y V7uw U JԔ:~lo5 SҭOM΄prW@ z~/ʣ~ګ(dg S qk@[k4YH"FHpX- eB)]@3"1>9#d,"I.kj #-Y.xC!FkmZq涆KRѡ>ڌQ@h@(0ڠc_+ dY?*bBhސyV熺;K^x1\l%KrGu\unLuncǎQ*:p@OOOS\vm ܪe+Rq4,ߺ1hz8RFڄQ m󢆕틶zSؔL`mĸ ӻv9ta|P1qz,0i u$].JBH66Ak7`#b EH[s`#0);F2yKS9k1"cόmmoolhhݺkAe -,V$LNm8BrU[ Mgw<|UVx;3cg+#g{,}k Xer/(ͤpH;VJU/]ԾWEfRq96bPhmkik8>|Ô']_'ܳlϏnf l`m25(%' D497ÏL,[b}.E,F qFbI:trёkڸn5f箝gsr^=Yԓ?tf@cU0+-9ڟJ7L@ݔf, \淼azjDאԀ& Bgc>}-ՉD8Ulq|o<;32hrt&l|Y,,B߹m[6'~mNMOBh< RAh* F2*Kł'&әQI ZZZ;v9I>8}cU (3M7%.{ 6:<'DMuɥ1.|:zD^&g6lCܼv޿gAB&j;@L6 R\QdAlBX]N}^kͅ3U=}0ӓL~;ύrϥgZ,IRYgҥ̚kWL.2*j3ꮮBEzT2rn,YP(ck50U!9'ZC?`3 H1ZKCp|"{;6BJ+(dw{ pX1`Ӧm]ԑ?W\|ٱClؼ)b# p\P!AF<JTijn,Y|0?Oץ}'Tՠ臥@HY*ZC @]O'a>0Dbۺx,UM02DAkeɔ)r8J`@ ܷHUO qBfh5m9 9" 3rb-g&VZpdBZR|iܖGFv=\\e&ƑlO3ӳRsׂLT׬]bneh12$j-M@pp V5}!!qawÂ.c5v-4pɅȦF"Ye\\s7ޜdTnhcb82';DeOZ[+``)i9st` 42GgҲ7u>UȃXiQ@5`) p;ZZG'yXgbb;3"%A w\OšlDh_«x]bX[DeP@GVCf$ o:;>9>>Vga9 HL/}P.-_׽˷/lg&? {3:2[_x !00TDR:z k=x~{3u< cgqL0 h}߰bWUp~1?a妦eKm`8^zǿ 8!rf A1;UpP+'+ctm޼}r̯:ssznL/g~Z;Zc:`}/_]qGTaLdd#c8Hh-"#z dp`2;ȝ ʬCGO #DfIq {6lhƍ-M |{O޲qÆkJG--~2֚HSZEW.jimOgRy֑T-,9!8Hzz90"Q cאY_Z!pdaB@hCe380TH 7Kh}o{[};vGK/[|uX-*:M>80{^EdeQq,\9gT )ٖiKכmݘ3⻟{_RG@6 ₥?o®.^6hm57mݾ mZDam9`m1y"GE4޹\L(M%]&Mຊ1h_bwh.ݑ%|>%B sY9-Mm]ArFGR>rP2]ءNf3֯g{z^~ljvl& HY.1C:2EpA Gm2din*9 ؜'"S%E r :j4XCFX`ѳY ##mmic~:%Jh\ö,q`dk!ےsRĩCy^iMhiFE~Cjպ VBC{cck% 5051=60rxB 23Ģɢ-:&͵T<3>5|avrjoL_쳳GOHJJSL ]`kbhd`lldthAyca&;942|ɇ:)DF*COl\ʖTKD33S繛^T0,gSD"-ϥqgm1;"++zs,6˽7_}㍠Mo):[v>ܾ}/0K&d*0X/"&f< ٮ%=3cӓ3 ٺс?<<(q>$DyOShCʝrr||iӂM6twN g&$E=sz#7\z_y~rZ?}^N6 W]~IkʫSS#$]ODRG`4Jj_UO稒$p$mmjezv<_)eͯ) '{ Ħ=S۷mte"*kмy" >XזyG\??+|.Nn;GFŲ k2&s!8bM%o)ec B&>0=sADßԗy`"Dʈ_o lɚr}]a¥߽w~+lok53i_ n(nA:!5ƀyʐ$$dkv0ʴr ;zf'g>}]T*  ,VcRόN}+(i޳Ʉ=d OXP}-Al@i)R*VP?}ypp EKW*LfKkPѰ97Jd K?A\k0Dy nxN5L:Zp3MMMtTDPJ'=qcca4hv>y:͋,-` d-Ѽ ;PmaVCjBfJRzQk%:QXD婩anMD #6$L&I߷`+yKs`ZHcfuWxCB]Laiњ+tfLDCR [ֶW*Awuƛ6]'?ԩ!cdV|VpUc#thn ZZ|$A 5*Qy#E豓{w:dbqL>(\~~/Td. FWGŋ#, )ƋYƹ ch ZfQp|?S)o?Yx !!WK9%b-k~W80KhPH{q5Ls!E!9am75r=?&dUHfM7jokoqK fTr]߽6V`(3ɬ 4 "͈XDN< >e mi<~T*){K̳d3ڛn!_ҐL ˅Y7I4' ɿ b\Ѻ헸\2eLP@4R=x;"O>-Cxx;6_]&):7;w57䮸d]}?\lTl%Supji̙ ~g՟O$?{rrؙ 4IeZ;JefzTeLC+oXؚZB~ױ5~u~% RefҮ٬w-;=ډgΌN+T!ӻv<~Ps]+7-}?Q?ډy;{{+tvv64~ҵ 0GMOL MME5,0#$aN)9R rb܂bRfKw끽z J Dˆڛo{k/]\O4?vdӏnܴLBytԱ+VerO[vʕK@ 87@f4A%S⒯6CDc-3D: &t5!q <ME;?̓'97Xc .t_>ӈ")WV<'ۚdślX+.4s;C8@‘aP|3Ghͅ*48^c".|Tcsn 'Is舵v_HZ*(=׻",p esq\epVjzjPWM%_y L]Q"c$=wE@-ZԶfŚb9~'`. °Z- 4G7A-h;v}}ƶP(LKAM]|e`( '5~$_aFJ$  +#ͭ8-\P4mK/,H:D3s b] *7~@XZӾfIt#"ϳ|Lhdcb~GМ@`TD,0~'d6܂078 G)k溋۽:ULM(U~+_u흁p|f6B헭г@uΖGN>kT0Z `DGOO\M> "1 "YVsdL#SJIP\[U'Ne˖ Vk}0vD+Vh,"C/;rd)+ru]7htPNd=Ͻ@;wkwqzJr% 43 X12h˄R;gw^'v?LPH m'}ɄyK}Cs]yOd^{s5+ ٦FQ¾} th˗)MeI^zsu{\e}|Ҙ.暈D?4ќ8WK}'{ON  hFze#T±ř0P. Ra54da?@ozSz)oph1yvtiz5[IUB $K8ǐ!&fsLG@|lC/mjj3NNjG,>]]Nw}?zuC, 1,L]r 퍥 նk"CyÚ|McasB Qk`D0L)E$$ rI6tup֖x)U6S8j8WX|ѣq%@ws t1_p}KKejhNO?y2E+_s57|M@4EŽWpgN;oǶ&0WL+ b!8@\,?;7~glb7bd'5{DƂ00}|/f/~%]6=3nˢoY 01(]g|8(Cɸ<\ehFbp+QdNѣ''w1`ɔL %Pd&e f+%S[jr>ghL&.YqoožRС6j߲umC*!|??!3s^%;W֚ާC#>0{{`TĹ`řʮGy ђvO!MwL\,'Ƨ %7L8OgJyWp|ʭ+?OjXᳫpkW_|z4Nۂ.DhmMV|\cx~a`pVlXwyoozO<Ȥ-VtIg8'' Ϝ9526'rLĆ[ڳM9tY5PKM$DF j=.5x>H8m z0 h8N 1C4'{nHT[Eg[]axЉûώN:$m >)2C}͍q1 Z`J)UDJHa:c:( QJ]߭oo^elpRadb,vg+0҆[K]S iƹړhښ4Ό3]p.!ek 3SB"x%ی No۲k岐J- W[cBőkO?5:5Ν{R/Zo~2ះ'-xxVښ?ڶ!G_(B܉p=#'uXt3)3dj5{/ @{(4QXP33\.є d0pkဃ& gVٰH.$,5d:/oNw.ر{wUUs: 5.WYKT]{>ȓ;v *,8![Z[L)p .r^@g'Gźڳ~W^s~ɀ, IZ`@Ā"GƷ;FZs/ZHıR8K1ȠF"D%K_ޱ{.IU-`р`\#@UFv֋Y]b0fZÏ?ϭ#[C.*(bْznEڻ[Г-9W-ƒ'8`!_`&[w嗬[>oܧCd<ܚ5R c{"o?Uы:W:vTO/~|c &NLA9~jjb3Nhk Q/Cmc,0M07bǦ^]]SJPȘLZKz: Vէ=cUe Dwue6nZ_ߐz_<>75?8;6ys9W ˀ`PUizj `BqSS"ȓNLhrȂV) YD0=εMn" -w\u+.zgIzzJnYг[n@2F[ H5KK6HaaX)i dR+%#=H `ksT2V*\NS2)7]@@?"Vz;&yCD @,vEŸ1J/؈zEF;XdZsesc\*jY肵 R9Wj4* O;!XWH:U8|<;PH`X1dNX H*jKBJ E U3bR풞e]zLJ9 &P6[G 갢FjEg''' )O8L].sE=x@ ZYd+C PB8ǎ q2p||i>;\)՘0| .GS}Ώ>=s}- &V85a.asK$bQtd M''Ϝu! QP]tqﱣ3N*BJr0k765fΌuk>gboaꕲ$1OuGH 2ƭZ",JpTDG[[F=;XmYOx56)kK-{th_|S<+ +Q84:ڒlXАR$Dāޓaqk/쪋Hp9d"&A,jblӏ|=ف GAh@8fʳA4PN PV¡OC!8> TQIr,WBpd-]yeP(2䱵<' l,p2lƵ|[}7 J8#Op,Jm7~u7_ҙimĘJvlpȁ#CC D¢97踓] V-Zx՗~a*gFlݲx`VY bTh 2rX*_Ə׮K+qgMÞ, &NJ-HDsPKZ5%ЖǏB$ss~GR8JS)-؄ _w /Z*gZ76]tvv\Vqvvv64եi@uui("맯&4V3@.8֜,s҅Yx.YOu0WJǶo(8 Bj@0ZHaue[6 R`3HĀ(*,E"Ɗj83K1ϿR'X~Uq4֐j#.}Ɵo8c41#^ t^X@Dq]D{Ow'wVJBD@eJƢ8GNNNNNp/ѵhI.!.B~;4l5ָkZ/8wzBMK ܸ"FI>2k#~dpdl jk'qMqE|, [ۆ:w/o87fB;1ļo6 5#n  $!ҡ#n]&_.gsN֩Fu,A)"C$CL ҆ &GN\p4&&uI:wi d+d*j{t%(Fq3,޾SZIgccSi1eA9B"].Gt~gY(\u奙l*5״u3gF&ie>?9>AH9Cg% k(,̞}DQ) hbZYҒt>p^B\=wݵj7nV*Afҹr|rG{DU0s|vJ𖋷/]å[nt8,9gZd:ө40_s7g ̀Al]˶ JK;nL̠lMWfS2-7%W}`RtL`LКkK.;z@CzW]uEsg3y7o҂Eh9@h#w~0&Ɔ\RJ\аiMq'O|w=;2015(j[Y#-$hH&׭[m$߱y QA|˭}[nY܂2  V A-Y.KȱQppjuk7 (SS#1Nr1Gz&Scznixɥ/xKO=B^sݑSv-i~e HcBHFzŒ&1`*F3-tY &.bPHcq`"̉$Wijl+VKԎOd WwPR)pkVL8LX* J#r[55hѪlT zLyu9"LRN2 akVfÃ@K]_՟w/:w\I5h.\ȹ yx?ZHd'і/u49zk;W7:;+21h6Dy/&WUlk_8mnMq4'2~!RdKP)%ÚʾV36gbJd2ΘqI~GAJ` 4&Ԗ,1p:~ θ'Zc֠=b;5:ؖ2\Cq>|ђ\:;=)?)SA}gi٩|aj(u&i r#(9@QS:$(;u27c2Zr T )Dlqn 8R 66|^r5XNJǝ=v ,R:Vֵ50X5egF>HxScIoFhon_}EKǟ?314O477p5KZlWSj"`qR\S/6ڑAbD@PG}SK:n6p]';L&nV*XqZsDC$(xjs)zO7f R>Nvquvۭ/[ּa&'mA Ps]8H뗿RTw8e5[6v.mDK`m/ |s_9uomǏf@Ut/rSz "Lo^^߾'01f\[mGBw}=*)/Zk[|p4x5$t>78:92>YN tʥKa9'y-yu@"Z$5a"cv3 6Z.F FC!,rPcSA5F} *bRDVcN 3[7۵sOXBCF4Q' 7@I&v$ڛ ,ME=or|߸쎟ؙU,?Ќ9aIc`5us+v/47WѩuYsɀ e=> qplZ .kil:~konj̎ $2b@dV^H$Cw\|Ŋ/벟\XD wf~z}~@R(,1lCS4$rdgO+rC%W<ݹI2dqѪ ܝ_u+VTw,\08w8gXs`92Nq2 S h1WBBBb—uZT9+x,]emԶ D~N Vi(ڋrNЦR u kYӝMpR5*x(_~ة}>wha[[1 ?a8L2yDvtdPXr ͝-]mn7ZmeB`+Pa#DDh.Zxq7(тW_+ g 'OQCOi&ݪ XPٸFH{YBq G=4:0=w7Qx bA *۹JkY?8Q @JSZЃB>:sf͔5>Dt~% uǎF+ #D@RtBuԢjX=קPY UΎ@ DdA{Lg~>f<3ֺS,r {wSu/Xjw[CVuv+@sFc =Q.X,Rĉ4 FR̞+OؾpIx;6NqIi}Ȑm)gv–:ώ Erm$[\;.'`R?S6?>‚y1Nd/Z<=sW_}e>=h]Xp'ts9\ g)/ _3T-[J?-Vo]t몥JF'&F2a5sh9QLu<7Je#d)0Z rj@ |Oy{O>p#&]}E᱓s mt3&lͅֆ@ 9"ha`HQ]p") O?x;pbfA*r@*0G:V(WgGLP)ʵud684pkMG5-|kh U:Hґ\ayPe*bJ3Du% Ƹ(GvJ|[eM6߰墆J3kۊ?dt-à +d Ʀ󥩂S ՘kk:fDsbR6643rf,\b̔Ԓe+'&*a;V0L{"ݝӽTrll|ҥd2==E9 |Sg^:˗-y{qMrT]Ɗ(VhTlx~z/յ4:/Y;s,iMJ5҆{f)MĠ2 ohhlS[K8 xdwם3V,:N!"bUD(M`,J9Kfԩ/}rX BZ7`[+?w.lc~>=Z*)L(2]Ə|nI4H2RrV#AƘXck,b`\ - `#A߫ s#̹ZșUP(7u,^X_56XL=Տ>2'XXӵ#HV[kiH9s%)m"={{0Q2T5Q[щɑɩt^:Hp=\/S!լu9xȕFHt y3C~dbi#QXRQPgJu ɗGk*++b%M-ɫө8*ZKT#ྸ 伒 3+dSDj>#j4ؾseNW!ejc+#j+!ryJqIB\!8֨7f'<!B $ $F\j\lʆ3K3` 0 xwwmJV*Uwl\,G'O>A[GGNMMzL' qo\/ B:q=U)Cw嵐iYxw "m"q-"KKs9ko/D&_tJ!AN<! ZTYwڮ۳b%q:)׫3sB4A:֪pΝ>9|, 9JO="q?_*]ηMmY@Iu<8I9C` y_?Շ3<}`B[wH`#342$cFT(pt'loxǻu˫nZ7pA:,Iud11bmoƇΜ>O)jl3ĬD8F09v.V|t|ᑧN4-Nd!\29ejڷ=/uvz͓VS̥=i[n.e5;:wG.dN_Z q ڬWGLŢҴ+" k9( ܀9)|i|r6!+d|򩻮)4s[ KkaFUĭ,-2Gbۚ;z埢cYڒȸt KszLڸ\8139Zh+k1p\:*L3gO; ,pd2 )y&pxᇏS|UVp:ڻE\Uq591Jd4"7&@"ߡ Eo7 GaA}_-$kuj{V aT*iZzƍ}3ucoe!!-0xxqfFXaS[w/pqȈ7Ax%tknmc_B֞1b \ N/_Y[Z(ѱL-- Orb6LⱱB >rijJ5^w[v팭I d\:" ͖KJ%RDž !RZ-ADž(\ I"<%I*Hyt*Z+ 0a2c#)J"HI AB01K$=]mT!sds]1*ٓFΎ~@Z+D1n <'l616?TV/n udz$#Fi Ep"VIIrDH16R93U>?Ro"V٩uܱF.b:FKZq. 78_]$-N5.DX x,>r%bҕRaѲH+$WJOdn&u'I .]]}ݝ`)QfqTf3 yi q:w7o?wWGGg&|ދFSىSc%6DL(@bۚ ܿ;ReD0B!bnbA=53h]7$H8=;4_ʹ|߮@zI:] 30@FP/׵J8-8gGto1ݙ|8>R) fV/I9d2l.ʝvlټRey05w&d]<>4zjxgθ^n;ʧ]T9/ՙ/NTY5>\T (x F'ˀLЕ+#/3է}R.u彷mI^oeu3f؉lOAK`5XsI䲚`\@!6Ap,X$֯^[̋Lvywoqog 1LW]]?z!^"#ڃR]-U`P0"BDžDQf&GJ8sΗ.:(ˢdD5-\mff&KF4/%O L[[HHp)D$._Moݹin7:|_w3xh2pIs RgВ+eN8R !c_f|.wcbfm/NB)SƂdnut> v4w]C:ٵk;"ڵEI={];v7B>h8byz;2?0.aȐ!"~N9'u$l!0 WA'8I)kz|N;]fas}ϓY)Scslc~AR_sSߺ~';EFx*73h%¶1[?j$U刣F$VH BE.᦬|[_/|C*+?15}Jpӏ<`x.WlOgGq}wl"N` 7s7(㮞)(J|י\d|Ri%@ZfjT:mn2)ǕB-Q4=3l=w6ff& bjT.NGǚj֨7֮-:\\ko%**DMGe:j:RbK,"֪U)d3n\"!sUB(ؖA.}R8z9{ܩGF Wk F(Kt8eʎF%F騴`6ruvߵ)F2'f-,,rZ)ItZ]r`R:FN9n1WdFC0]Ar|6qݰ Rqju k=# ?ck6UCS㓓33 !kj0Z"}޾k{@wI+EG:hU]OTUrGS-5k.ȳ3ܘ5=ݽ]J \! AYj.% V3,I҂ǥ\JgPDK48<1ca5Z1rq"󽴎ښKH6q}__=9|%O98;'Bh5xpUH3ڀtQL@QFkuwmn 9!`Ȑ_̗,,m?w #jr\ˉKûeݍ`,Xb%]Y/Vk׳*j/fS_scGr)|?|ovlOg>TdAe ]{l{L zp.{%&0,n$gO !\}94~Jh#ttdtljbrhH ɥŅt]%e,ҋRqu8ɥ\-yDn{/_Yep{ @GK:y\e2!JiDkafqe1Ddr3L2ٳg>OU44<[Ȑ1Ŧ h+v=c##Sl&%DWO|p7g2i;>>vmov׮:"8JUbW5[w/]]{ڳƒm!3z.V*:!r R ̠)#.7X'p͸Ʉ$2uhT `"˴]k,6 /_=m3#kz~Bsg׬T{m)DMV3bqR$8HA2M DNwmvm ^i8X#A2lԚWH"=?;/{^w1qiOP2Vۋ<* m5 .![1A=wqƸ 80ƵRq,= D+ӿ:/m9ܱtf M Y9ojt6jaAi<埐^u?G$n|d(vGO}z->Z1(ťAY5q:`Zp(L Wt jH2\9}jdLӨgoYur`@{g{WZLrkc@cj9BsPqb[1w 03yZ֦}?B"߿^zE |YB!;Zm1* csYD 2OXtYBHք:4F$6\ 4T'-k.ұ dc=S 3Ј1LRO 4-X"jDXӌ幒h/dܖgg. ԗ g܀ѵxP k$uf;th.ÜO%1 $vn5:MdV #vKhK\1`7'FDI"YFsاrűCİ#ڿ9*n:(e^{ d9B"@!q꣟#|Z?yOoܵglN^47Mt,hcT!Z˗R8RJcmE*:QdEEܱY(U(f(e- 3Ԗ:Qզ {z{z˥R\ٽsO*卌_͘*( ?{)$K_'?(/%%B\v?/ᾕPnWe}bc\Ge=1I\o6&%q4rd >/"_>8{??:}/%<AYݚ8K͋]r[8kwZ`4x`#X/dEFg?xIѤTn8s~᳧Ntv,f \-*c|;L2922VJ loO3gY֖W4OX4Rd ɥ/ R)il8q"d`[b!<)!$AD"Q\k68_6jj۶dž-The4Nb5T4bp5]]"0<$N\ 'R#LKek4ejtǣ##ͤB0mtcU^㚥qXk *BPH4,k1(t;tlJa$d8$ `ʸʕ4|K8=p}MRҊ,!Xl=[3n "9 RJGG՚cC'=kQGo{MXh/@J`}w0cBd-hƣ#=mBmnZ 袛XN%ĄtU' JBH8HUDŔLJmmX)?JsAH'e8(R1Ȓj aqɘL06޳7J[^85:4:Tdzff 1\m6IMOYXVkq93A.Η z_wO&  v%Yk5Hr#O;u̅b:q-c熦7nR̷;xK?]eaRzI =vK 7ԑT{s k禧Un\QfbWwZΛ|G=3=H@Ivv܆m.@h Wg[ؼk{miMN6.UJgfE"#| JOzvm]/[:vm 09g{>ԍ@Hת gOT '=wVK`.a<`B1 IGn޲Y{ٕ:Yb\sPG"Ijb8\ @qDbK7ZS)ΙR 1D $Ce#@"jҊ\]Q9nb[GqFA!WUT( 8gZE^>aAK(ItE֍+3j,c. f&ݰq}g{g[!?,e/X`Bÿů{wj,tdF@SG['6#!!#I҄旿roFn486 P 襋v1ƶMkwnۼ07O~շݰgML$W_7ݑN>ؑJ|CwmqƖ` b *b"ؖ/B ƙ'gAWK<-z 0.p_C>L̀]ޑ)vUmS\ 6y)܀䇃/\šXu(0k:9޾5oz]n^sz!p@Pf…q9N6pLV~ySCu8&sI$t]7f#$@!T]{Rs'>L ,@FԐ+ri5}+ľ]IQ5wؖDgx ss3펓'/BXŜI^/Mbw4:7|/T;_~iFW@W15C!$ `!$aR@+W< xU[q;?K4kHa?l9°V6 ^Af- 8Ccr%K#`N kUaMzYhϴzٻo^_[ʥʯ,V> |η_FZDjBp bB("ȹ`b~_;23#}t6d}٥E10? Bgr`\"1bⴜB zZ$!ET#r=k#)5_M8hHrv%f[+ę9ȶÁ.^(ð2 ,09"qD䲘iƉI\u礫$V0&UՄQD@U6?1400|a^ixN,#Ĝ5v_:4_ୁӈ'>͹x)θshz܌ꌑ&eH #Hcaqlo˛#fQJa -|y1CQ37TK bW0?=8$Mx[!XEKB#$I\'t\:?l :bg#ՒN LX פ'^7zms=eKD%xH([g60c45;JRr`w{sL#JK#S3L8ʒ6Jbߟ$h1 "$n [l5bC7'|f0l8̘s]]흒K#e$cq4ju욝f \3_MX*AfG>\r%UO{ k2hH;(08x<=?[=f&G$*rr` -&l"8ՆbN>_|h^C4pP (]9_f=wl[ϟ}.Bg5 K=\ \64KRD%/`Ѿ_<\󐖅k[M-KU}ә\ܞRFe`:/aI[k7l(- lu%.g_ o.iv %j*dQ#@Kd@i,#4-ϕ(0RXJLUFŹg'fX At&V,vwK(O,L/U"]c2ͤ J # Db Fk-e " hU:T} Xmx9؄t"0 ƀa˪8~X KgϞ-ۗ0A3fbp~mg=w9y绖 ѨISȼѩy6;3_ևG疖D!2k " dpW;"Bʼ+0CkhqQi,us鎮ΩDEQíI#nf&GNjٝrnٜ)JB8֑N8rI|_- sz=((42X-g)Ka,N8пyj\(fS& !hk*KgQw/UʮYs[gZ0ȦnJKzh& \scr4Eh{Q nyN\65AD[?~L;]H,xHFI&+ijRFy2!Vo&l~nX=az S Z EC*0Y/Ͷe{wqF77n};ܳo;;<5Nj4<<566DžRb>qJe@Wd|ŨG@`A(J"& 3.lc-+F$r|~ztjfta ]wpizڮ,ta]w@wtMܑsgF_n<Bdlim*٩Ʌ9!s,(BB3-!kIpqߑK>ʵ0p42j D06of<'m۶ErMfð13*y2Nq:.7F0^3]-Ҁ^Ѭ+CKQ_.>*ޡ J1D22 2J=wh-Gn$`41fkŎn!9Z_w׽.y#-6=O=΍|}~3|kmju%@ KKo媈q `58Yj9 "տqyuk6}yI$˄\JjD3,<l#sb9dRhZb'^-w/Qr\Y|AzAvŏ###333rY0 YT"k "Cw5Z\ԥ+]O ,EKB#.5f* zKf ]m]]ݾ;BDQlF*#Jeo@lk7X0pj0fiT8m!q!129'c9بZ+Xǰ%ə̗OfB\#޾jzXf-"?q䩳L8(" @- M`f`2(|TBG "C߽h h.ft+5KMZd;vI$Zs m`5X@r"kkڕܩS'Ɵy*rhVk &Bv/1 -69'%<SO0@K& C7|6 N XPO7kJCO=W6EP gV|4ٽcǿ:p0 hhP0H:{N< ??q7LO]\J<$lڼ fXMh]¢cSS^qV2@9dCm-39zgQZrRMw S+&aBwۖnf~7 Ь_٪Wx E4P^*5 ɤ1RuΩ,aY~=50cA4 "p׵Wu/LHZ`@ZK7BƤ)NW|egk/d8 k5ZHᅙNb)F+l'vyWL ĸdL2ތb8J c D} X6`#Xq`߁۶ ζ|YOsj<33fmOWgGOow[[> Oo4{d͚^mCj7(K"PCpd gWH~^*@BHC&I.`0Lq46_֐"j>!`_bE'@ +jMG+~*+`@ X[vfPR{徾k;?aoljxvnޱe󞝠4H,Ce{%N|p Hqp}6/~s_:}TZ=u蔟.t~ZN_u_YsC0(b.\cZcq]yEWt6}adXH<:zu;`68J%$w!2H!kA'O>\F۷o{ǻvׂQ J沦c:qs>>⬿#w`k:zݬrlfb#r!cԚ}S(nh>s i+d=[ٷqӚbبmyN}:'3|Ҫl4{Ǿ㍄#HXk4-B,-KꗒD$\ނApN-1VK ʏRÓO;سf @gnv z AwGVSk  Tk2\]_q _!$ˁ? R[_W6 ڰ\.-Y'FeVȗe2L͋|PȄ22d% jH e!ː!5zf_}ն-[TqQ|{{֮]ݱ}}}aqlFa:U8x'JG;zD^*2FD\x-I"^!2P8RD-'%A[ee5C@ƨ`5Vx\ԃT&Sʀb8)ۼ.q?y?{ڹK_`/^&fy;A20X%\Lǎ+u((u ~MGz^|k,7M̀UJf]UJgn[sͻI\ecˈZ".K{^.^"ǯK֨\uַ- O"JBkZ\FraYɢe Z] F0$i?oIwoa ϳZRN?c$NEzG8Kb{~-Sntvq47\s$ޕ5/N/L#|,Fam;ڻ.Vi.e~IV{/-M? B2*64JR)!bfրix9Q(D-yI{BطMm{ge#s0-% ߯ekh|a@Ξ9}4keLe?0iٻ{hKowgxl\:t B&Չp$JGE Ђ`!;}F:wZmtuً'3hfE˦rTk65q" (=֮\3PȺZl֪e&k 26 *'t'O\8ԩ*I5SH[okb(jup`-gyV]zݯٟaQCg.M0˺cƉƴ!m/k2z٣s3#`slKFXWSSN=Doݻgۛ|c{Wn|zrS.H\!0ku-  DJƆϧ<7rE'A,.-ja\5 m 1`+2ZTʖf&HB P%cDH 4񡳣,pI굥FS,H'leۀZ0JDO|Q'c|_߹cώJ}iöpk54!o+m_YkHe&\} `ছ0 r1>;?25R 㥰d[Lv߽y+zpj [R[_x XK6`mذa3cuYժm87-XBv \AМ, * (R*uU.F$B q8ct;ټy=<69>y-֭o(tIDܜɤ8:vlҜ0pSNΟ9lVwսsR9,+8ltwj 熎-,Mdիa7Ėw؂D P/gS1E y@&2LbZaZ/.O.Tg/-*7UKJqb#/L:YJ įܸu dV Ba|ȃ_yǞ1QÅ$pM}9;0$R)A+s(rQz[GG?}plA'p 1B۔qЮX }QcoMcS[Jӌ X')!-N ϟ)/6; - Lⰾh@ pٷ&2yam 8X .LMJ u9d_*$uҜڽ?OX<\nmKEs`{;$eK?dIWu9P)!ǟbkox[ޜ+^ڛ]D όNVKF R z:kxip88NtZRJK@qhm pL&?5|өLЅ%(븉=mPIͱᎎ"]fM*qs^y3ݵk[t:hV͙U_s٢k~:ekeđd "-#|.+b[.le4yyhw 7h(mܼt~/e f}@OOwnؽuc>0]̬=Ĺ8yO|?e/,i l<2Z>Zu'Cr0p2{44FHPFgO/9%9̜R^2UX+.Rw5;3[.ۊm33sA7zZ-*YdD`YUZX3ùJiCȅ,:ed::*ݵf}t1ݷgBp=[QqpBvf:vFըcma5Cg# a!4uP>Ti./!""C3,=y$9y`@@[ԑcPBBޚ\)W9@ Á֜llfR}nݳ}dnۋ[{~!@UnU\ mQ\;:~Jtd oӚ܅G?}itZO?c?7sf\M=\,1˓5H$F%˦6\iߏ`,h:5_? x33s%4QK\oTj֒_ TZ4td1JGB#Ǟ^h,8m޴?M;6EZV @dVjPSҮ~xgf3^AIڢD(-}x~yJ- dL MfΥ_꛽[n.8Tsb_bb# Yk8r#??bk*ܸQ&V5(]a9βz&1xY"ґdA`US3 Q2s"eܴ-bLqF:yjgOA·rW\幘/we׬)v" Z' Sb`ܣ?\gl _ǀg`t%-Vz=lɚ@:2MDHWh!c[41[pK=y?@h8-~ ĒoRf}O{"2LѨvww_{aDv…-[X@g2H#ls4VQ \u zhXk5@ ,k!"c i<_7EoDV*bR4/֫ W"Kh K)2 g-n6]JeNڂ4vcrv:r\1QZ\}kkSh em^´<[~J+. ghlot堑-8q}?XJL:~#яP!޶g X\枬.u>[ 缮3g@#2: z=ՙt%.+},#j˽S$ #Za5 pDI7FPJ eB;:7o~7u.e{ү֩ىt>GBARpŸh.ڰ6q.E ͅf5ko "΀YЉ@}Oz>JHR _wlbUk@; ;y i'% nڸv`…qFՈ3AjӆAW2Sͧ@`&#k@˒rj'o ZF\sӁf#g/,κH t% yxJ$d6͝=s?#a;]oپiזsw ρJZ(  L+$c9mޟ5 zSg'oUW}5-y Ro.>ElviM( M wȏs5`S'>Z@{g~ vOۑjlt( K&A 9B6幕J9ߖ3qrpOuț|q>6=?ӧ~鑑12XwOoo g}<ո}ӟڲuW *Bvո&8I_uSi0\zn^g?{L:˙>qzF`@Gk ^7*l6N-tlݾ7ܵh9u+#(B,1_-NOM}ͅ銩 vvd8' td(xg&>ŧ}Hd5rJq$N}n8|`~~Қp]6Hgg[5qg)aYC5dZdRF+ÀG&q-bZh!c@,đr`A Vb: fs_>=25[4T`S}M;T+ȥ=kC|Z9q%T[Jlg6zIظ']Wx 9lVnGt\B \pȬ6Zn .[Q78 j~șO|޹E%0$-k_W/\W.ǁuU2癧t'󾈒;F@j^HQFVo+ !מ.A ^Oã<~|dl0QXj4{>xڷt6 G:ѥzy5K`#MUo6rz׬q$Z=\•ajZ&t1 b`XmIy.ddxhbRc,2 ۷ᦛ6Oӱg̕f;B*)R|wEA>{ bB1;:3# rd 秲)`9UT%-g[^svJ/,lޭj- Ww =Ƚb )!ԴdT6S՚<؉qcGon}מ|͚u׽|p Y2Hĕ(,"T!?B6*g~vjjfvv~fzvzqR)dZ U7z{֬fI'ĜYDD3&M'DJ֩N>6֏ !\k$Jka"m/Yl܅Zuv0pm3{,F%jaRmDs5DxYiX" nn4 e͵Yd"" X29N͞_hx;HPX_"yϦ—2p IbH[  aiLyrGu&''<}rtll߾}!Sm$=ij[,ب,C(["iJmlf3|was炔hN<']6Z`[[eSxx 7~0 f|M]9 F%~A ʒR`Vrȸ#^ߑh7de 8ri^8dR |g p$6wWpR⍯}#Cٮ}GO9=0;yr溛ټcXܵ&F~ \tfA dcг]?57UXWGC{kHc]i|ǣ-hF} PlCWf&]I,C9#4VRFLrD&&L\W%ع'ʕiD͈Ӆb{j+l:dJCs;D6 #p^RK3D'iMt]y_kKYbt:gr&d5es "urnüREb?[[T~}n:` Y6\$=+Ֆ¦ҊΖku\^+wusd #llval۶vtu~_閛ݯn:JC[UύM>S. ?\ڨ'ͥ٥|3O=~c9\{Տ> ][0 %ĩ\6QZJ(ָ+$L3??BNԱ#ǟy9:hy{|I;|;zthfM3恵H1@_>sBg_[v'-,S[ڀn$DFT_pqiv.A*nt2[踖{LzG{+ r`PHKV~m77Վ.l|78sC.vu:IffGFw؞J$q\ނ1Ⱦkh^dk,1g~& Xd LJ/U^T~]U!CgcF)׫2Q.<_zn" E 8h?qjT!ɎNq屛~gbm~*jǕwj/e4D[Hbꦛo߶CԱZ)$>znO'wwأTDrZ0ڨ*Get {z55=6đ'z#2B:|r@f maQr6##C/KD p۷랻zk{í: 㥅e.:|tppւ1 wێVUgfKKl%l %6%#cȍ1Xp ;w}Xd`jt980@WN-3'".˃@2 JD"T6FFM[,})?$Z d#H.͆!Jqj&cc3(g}g=ϻkmp}3lwpv׺>P*)WnVO?ã^Hy؅y7:??373?38{nڷ[5,xqsd( OBppocdqa,JǕ Iu$KLp-(˘ _~P[:n6΅͆tf GJL%F:^!rs/LOO M`4gdNEL[Ui-gΏml.\ 1GRW|.Ǿ\^,my 2(ީ ]nX4xiMZɹ}&6'֑jkzq\m߰eS`X,t5GF"0q/'?~(ԕjM2}u0D%[fQQ\Z\,` iھY Wu %HE,Cٷs[{X}3m֭R\JL-pH>lݶ F k ܶ&M$Oo+}ʾߍ\Αq$w!"b1k9 bW8-" Zv`-bՇ˰w_zDV/w#"WIx̄$*Binl Lg 9q{3Orii}kvhΏrm,FmBQőR*Hg/`|3GqI=SD7,Gq%ƄPpD_ .͆&z#&E'Hf~Res`qQٖčDZa5d\E(-L^<ztB$r+F͎]3 ,rx 1H?%­VWnK9{>@{]w4W:/|y>ibaQ#0 6i;m|ӛ5`kM 8PW+V˳ߓρэo4ɩ=sG> Ci?[+ =rUJIMnLUB N?Sg(so|wؖHc.PGSO>}hg,V'?16:h43?w{: \ַyfn𵯾i5 #S~,td-AjD1(w*&icUbFbGz ]a}G#7]j IS A~b<;<747<:^7k&I0@ DDlߎ;6n㺻#=G0;~߾ݛvx3iYD25 { 3W x۶moݸ}# dXd_T/0K?0Ņtl>m|nn*DM 4 8X@sː1Dvrvafvvut;z֐r\v^8H)mٲCZ;o)tyޟDj~v|jBi?/rXG#?{RŶũ b@: FkC:Q+2cր 9_I[˶J3k‰K3/܏vT##CsK4uRIMLΝ>3qœO-U Z!%0:pMfman-sx/\}Ϯ]l,<};]k{o *z䕚g3̎Ɵ-o9EiwQ^QbkpYAH)כɞb@ {=n ]JB&r3nrڒH,3g!1}ϯ[~mv]3ݑ$L]I*^[MS-%BxYb̽]"Cրp^k ã~{Cj I㻾5]f-{8y mݖJ[n :KKaTo6mN*AђfKtFDJMw[.tpQʼn5 bd+0s~ Ů]WfsV^î dI!i.,N*x6ߦ ɁY^{./;>w^׽:y,1{y~~'غpzr.+ &npGp2M_4WtX|c}[\ץoKM[Fe| L*ƱaCsj|%V*,lQp.83fe &8t<0%O:vwJm}ɹqDʦ3y\=UAk.%[W @g;wU,х[^#U @2 З%VeP/oTJ|_SժJe(q6i)2RH"T 1A#@kYc\`X3P63'2M7s 7n>t8#/Ȯ.ro; wO;r]Nшwxw/fHJZ >Eh5CiC#88ӕfc ,4rWhx)]nIIөBȱg*`(ڎ=fGg;lRXb7K ɒ~3'0ɦ3wXiN!уЅayKgܜ+ ZfWLRH 3p>wRx\c}pD[.JwKΓ0JB`ȕcLJG'?u!#XC'չndB me_\_[I/[Ij== : ΅ac=L{ X@V rGM=}lna@  f{ۺsa3fiv~*͆zM2 /O\{q ]bXD(8Z/u;$Uͨѵ+<YB@._x_}ם.4d}_U*0o<0233 -CcSuD-.T6O %$`#C`\ 6s7UN;eR^)W^^}`pwoozxݽ]sԏ./088:<f#g캮еv9֙ z`浪-Ye[N8q\RyyʼnKcyD.,[Eb; z sSvY{g0؅#iw=gַo՚N|wWi}=ŵ6|C?sԱlWsqICt3 RV_&O|~cJ~6ZzuMLL1g {>"Qn"0 H k B=ۄYLTZު 6NPJ`&]\$3/g,NeZ] Hcp(m=?c C+-5 %7/mb:R:O;AhGj5պXX3ˊY ҭT*`(@d?1_=a߹3S[o[n͆7 c},NOkVkJ=# ,! x^f%C d.uWr=߸Eq7d]jf\G}M7]yRw#c?rٍoߺިךMS LLkzJ|!dm& Jw^t/uH*y S%65|?~kvhlY 1Τ낐cK.D1 d~`).y^ W/?GD D@FCJ ;e uW-^Yߖt\*p9#cΝ%@i;W*E;qr){C5?X>{|7~?#2 B`R},T]ӿTܙ{6]qt@FmiĎDk^GaG 6B?7<5yh ƑM*8 z]!_b 1Zw<;??Ghͤp X-z_itNб`9A8n(Vt" R77eW K"^I[ۿ8_>1@pݗ̀Wfnf/79i綠ˋTMx.P<EExAZt&-fnڳg[hR uDԊ9mfM6p~|a{k[cD8c Rxr|L9Hd!MƑ\y' 0dٌ5:SAlt@'D?' x-; XMD5[Z}_Km{ry <`VB `<@ s׾(5=39`F!e7'ޣfԵg''O-7nذiӺROA,sǎ;{wܖqؖ-tIJW=`U. )r\F7KXUz0RIk qﲡpb/Hwm+0=4*5\r 9j\)fn_"Ed?\oI.}˶~bT/.ĚU@B$ܼ2q# '~[yM$u% В X@( efZ 9|y#GoZLH-V2ܱtAx"jjXu'ju\ 1]Oɱ5H\%:#ssyXCbQ1Fd}a.=vthn٥ x quC <&x!uఉ?p\Xia!IB;W7Zj8vt<%Akk-\}=l."=yk'3IGFV:x♧K'dD85Y&9k k:]Jl -̫/L ~&t ҕa30:U*K٫w_Uce?V+*^K_^Ȥ} fݶ3#c#Şnfe8`9p WjE{덻*f9DL̔׭Y̱S_#J>7=ufb=<<:?X?308d.rFe`gժ!@81f,]ۿBd)Ehr\Ʀ /~8!ەX_fG\YZi  PF \"g8 |ȁy;O4mŚWwM\z;7+lX22$V2"H0ڵ͹_RǕ?֟K:L `@~g߿0ӵFŭŨ8d+, 4CfzX./o1mZT9l Z0EнغcEAX5j-dr>H u|~*|o Ћ< Y,Wˋ8&B[%J[ioWO5mZ@F$ݥ-ՅHGV{b4.kh;]2wb-Dh85>?J̹۞{RmJ-ڴsi4UYH5{V1L0?p ?o]̻~@p;ߺcMRؚ#VR:~+lueq EԪZeg"χ]6H\YT9wBzskڢ :qxǶuǎfwlHE7#A"XSKz|)!Z"?j9ZQ|2ů'ne0 FZȱ@d9} %"ct~pmR`ήڴjսAR  uf}7q;jdJjҕ37O:T[pd4[cYtLPyC\. XT78 /<^=F[$$m\nx7i֯.?|8ccm[7 9pq&q2Am*6EQ#zLh !/)9Nb4]Z2$@v,Jc\mZr+p6}_yM'meJUZAtJKǑ)Cܔ%6չÙBBQxyn/b??Aޑ{11Lm+_Ԃ˼T':ay? x%Rh&$fhu<]nҗ>/!GcBE!C h3[f-G MY;]4}Qi@QOZ|=cz {aqfd^_ofn ggNO{ZeA3$DnHk"ca5 䙁vh\KITگ}8Epĩoת\P7dzٮL1ϘSo)A08wRr)i 4cCwRVi2ZIӒjǶ[U.p!_ЪEAP* w'xrllo# y@*BnZT=oK 6ΕO/T˙0fߩl%ҚK `8}C]qm+dˌzh6߷r* +#! 9;D!_n]XhZ+m؉|4͖ 1ƸtcƘ(N#Ƹ);jYmbM[ eg=|EHJR.ܲ[ww3%C:fSf-phf@#Lx7] o}};߶q~v` $AːC95~683ge2qk,qh8p 8Wn8w0oxVGiduuԪ- y V]a_%V+ӳ3Bz/]]B;~ԙU6m^Kؔ։־$+˫EA.z-gE/폶0t$炈d uG̀,"9n41-n. Wz܇zj{s]2"e']vE`!DC J:4©;UI^&cؓO_O}?o_Ҁ%mč-j%".0+}CIMѩ'<=Z"J+&-JV/9[nڸv3yi4`HꋉJ9פ ! Xˀâow࡙lOwCJ+*AsAb]EDĘPđ8%2l-GI#g?~VgB?ԤǮim^?f u(m$8t;ϝJv I'&[:~]{Y70dZi 3nTJmZI3J1Fgq}۾nZ3[TTM;^Sʄi|]=w?~~C__8poFx[nyQO>{5+{&R dB֏bVUf6 fL]|+U*Ȝp֞AB ! Y>.I H#U-Z?oܱk`wmz{x6Mm0JY$56`f!~O<pARjm?Ѭ7]7C@&'g7o߷-*ՑA3Hn&G(LZƓTqlj㘨c"%$Vi-7c!E=ē '~vZwkQ feftPwWWWW~b!__\e]{v5\TU_f]__WUClV[ЀV8hVP.tXERE-'#k SFcK O- 08϶% 1ɸ3||[ѿurq%|_pm:t>ne:Ujdd2,JϊqKըTI?9qrfݶaK Bi;`^Wޣ/9d3{?l5I{x0eG{lL$&F\rDX0HYFo`]M;{ׂ.E)}HUS0rI1%f +hקhykX\(7J%mJmٳ}VYHsݮZc6ψޞ]dj,^$IBFc֪ UZcoۮGKnG)=r27_ټw$"*zg}.*^>Q*6&+khiylٓI3kQZ=xئ-딎W۲uSxV ǭ#\?Mm[73gmJ'ɩc;GzzVoL-n=9zv/9RMH59pl@iaL^ޒμp .tNO#Eǀ 0, w2姏 ;^x i:nOܺmc>kBa :$s6Lk{9c\77}?3{n ~XwȒ4u,)5[eYaLuĄ Y 5GW_5c|q''#Z&%%dȄFʴ蔵F$6Uɩi]b7-)+!àН=t`T{|Y{"8eVn-obL.Jan&4-ΕwIJH#czim-kq3) řVAhř E.MĖ&C&=3Fb>#}`5r`U[=wzlxdBwqЬ;w]c7BiۊVly,8*sGzNnW qaRK{ tJenXGr}!P\iHh`vbG6[?dGQ%?y%0qbnē/{xPo4ùkwj 3}}=By1|uMRY=ݾuSk\9ӓ}Qb!~/˓s?FڢE逵h0UMN$͈G`gEFA[XFp/ZJ!LX4ϞQ~'{y INZ˅9loB6`7nXԩfw.|W:Z%S֘kVmܴlHu-WzIכV=,|d!^)Dgkc8ZKeYe-Yd-ђjEмh P`,i$bwG8ϗV0_}Ip-.y?.:5|9WňKy)zEk.5J g3p4Ǖ4F~7թӿ;#Olom+wkjإ^ v30Ug'΁ =EmH:hsì#= +3'ʻn](L9y >9?7 % Pڱq;[^ 2 ;xș3lٜ@Y*(E0U/o͗5y]X(/,ƕv] ~{FqڹmuGMwCVG)sre4،9K4er]\d&&hPp86i=႓1ZȚѦs}$qB.?~HOOWFkׯ-tjua[/t~`" dMjU7mh'O:5~J nP sZ;Z=̄tqA6B|6b z.R21t8) DWΕ:&xȹV|(K&JF6i,0aqW?$o|fbn߶mRַuXczz3`bb<̄7rCp(`5qcdE[d|*"0 "JJ,~+V^Jbݲօ$v89%2 R}`NO7ԬT'xXwFQlB{q~7 0)ptD'k6=o5WU#*aX[S 1IdIN5] nBZUq uz;XuJ#8M97j.#\"q_5\"HGPFR J" ĉNҪtzK#@5||c!x_ϖM׍ ŬZŹlZ)F,zA0H]Ђ{!Dp$}}k׀hpWC*P2k޲O9]ojӺ-/}^Ggo3oWk6GS'96A,!9`As&)TY5I4=y21NRsaK#ǑВ~XIj3ͅLJ8|åuv‘غ-7톼!`^W8=k%igs/RV*.9MbP_-~W٦"5M2"ﻙVSYRR '-̕Ͽ0mٹf9?ܪ :ZϘvr!p2.؁|fHiώ۶ l^u}S__;:&?.w_c7\(J!)5:ӧ'?f6_|5Hk.ӊv#Lͺ!DƅA0ԉgεbÏ/6ce@#dXmzg2iZ?6z5&.]k+ xnk_GZk_;n&WRJ9r;ZkN=s$e3I1a vͺѱaPtR0 DJK-6`>x _³@/ MbZcQGN=LI MR "# dtl 匸+p֏ne˽[wteuGO9\@qUVҪKFiOϝ=u<3AT{f g@rZd!sQx'ڒ@-ÁvlݶyJynΫJ݅s2)N)R)IMk{9/9R$tV Ti*m}W7WMWK+:Zc -Ȅpƚ$Ijdƿo< t>3fL188 g"KGj]M֌IC)>ɯFQs~/ƍnJKNr c 1 ݼ%tqatUHzygO:Br膬p2p_~{F,gȖmd 2&b\kYmջGFD@ mY✷Wr Ng& ͡^kEq2oS쾧}0H#ZB kxoLf#Nu2)%Se !1ՙjeRkCbpPf<<@`HWt =yCXxӕ;w̆v85"&7]1-rz;/|Յx=zO':NzA*jV5W(B;cEDCl#9Cd(W B}\N}ѫ^?'MZvg$2 !9yu280AFST KmͿ׎]{~wߗl䉽-H <‰RUOꀐXFTSFIxaoW_.pK"sm82[ 1?M־ݼm۶kw@h6ffO>tpvr* piBNc@h %F#|wی3`$))82Y`[NaI]E8:I>?х7\q[(ᗜK'>*߱d%I,|R%Z\.1 iB0cTժU{o:cGM6lfgۥ@R_njzRBJ6m(4E2`3MJƙ7;Wibw?lb-}~4 !2%mȟC~e{SR5JٙBh'zxh,ٹGHZn̄-Qf2q&T8wq;nGBb##q8ama9L"7b ]"alhz@dNV<CjgsbhD(fvL)z-~; gn˒}rWikgem.Ä( {C'{~ @ |-T !5D*-_\ر=M=e27T^NO 3sfw`wOwuESS~ Q+86d !oǏTpX="TnnH㶷}{p_06RJk(R1,Lb@0, RLD@. cCX?!"[<^3x./LWޒu$_zBuXJRDR۞ ND v] GQy^ǜ *"j "UHn缠G>?m1md@Lֿ=ǦĀXB6oj}-f/h@&ͅ/Q ,fW,dX"`u@0i 8uLx u"BFkPI=ٹJ_UM< R& 4ƆK=8TUl Ką,DQD#H,aR4%IjqZoF&VhzJBv@ze1`!E! Q:a/i6D33?W ;IxR6o,z3˾]gf0רǧNOl"2 Ν;8r]ZٹB>زyU/telX$ ,HGz泟s?s?{"T'`XNr9X2mT *cT-5lAh*_\?9sJ5|׿L'Pˬ8~_llb:ZJW%"krcCBHc3&I:"(N6BV 8٬sADf{wYWa,ժ \yfUt=ȓ}ݷnG)C~g@qrDȂ} e&&X IXͷ#8d1D.-62JpL?!yI榧+Ü;'OM$ס^I>AaȸF7n\CTZy^@vcG-t,`HQiɔ|ܨM8Jg`HJe$/`i"XYHspα'D`<Cr~IVknnÝ׮=Nw߲}·O<3җ "9vrغu[%SSӈu!hthXx%vHO;Ϥ̓y:@"5D.M+=X'`f Vezs8p-#=ss{ƁG{"jE֮U<(U[?^LӴJdL<638һjXqz@fԊm$9?]6ٵyƍ&(F\ZPq:{J)Rg,!Z@F-X{^&U-O?75]^4 Am: T y՞;Gj7mpq|Sޮ=5Mu8p`7[6od(^5I'MZnڼcBsbfP !u%kg'aPUKd9΃?A X4k~ÆMVI&\4HW,ncMVo:ۇ .3q CǓA1s1A4?? %5BL۫{=w-eoR;0gՠ4Rp֨ƿdMC{~ tH;8]'#+Wz e1?8ojeweY9cqtO'X,aLKGi Xp@{#c;6ߏ,s%is wk9Akj;[ҥ. f%W BDMS:vlf Ņ/}s2&GR]5LS3!q/rP*PP$f=wd鞞,zZЈ0 #ܶ3!0MuPZ˾\yٰ}}+A/X\d *, ȕ{KY;6~䖀s O}|G< P(@p CCLCmfvjrO>itfܪիK[KwwyU0Y91xo  ?Ecܱ(@\OZ:2dI4Qt`#LGCk{W$d ѤvK \ }2%]LRq,Y"Hn7~n葉ӧgfqe&s T zN?1)9 '?}g{|ÝVHG\KJbEZD&DЮ9wvvbg>;޵;̂J\h"XӶ`$Rk 2V~57 Hm΋G V%dXV%kH` pY'K`VSRݕ?srfEjѲՊ\s}j톼#ZgH-+u;q~Ø8VBH@ Kk!g Gs$&xj"dQH0J!HdD0qxWL$oF@Xho{@­|?HSL`͖cOӚJYəkaϋ5ď?wi١#vXwy$ˇ!(K-X:MC\2q6 0xwwVcг^ DV fh ]ߗ'K_ /کN YdKG+5 "` su֪(lO__^ZݍF\/?p7f .Cwy-t\]Muw|?T6hk9g+MbR͉+Q%'XdZ\*·8h傻kmVp|<$g덚qU(䥾^^ȁ~`"CKǹ$J#@udkl5}u]l.۝y|ըgurX~w+]2x$>.?W"I,q "؂&Vyg{o/]$ дxiF qѪ7w=GSg4~JںR%qy6p'(WS{<Г1nw qUW#熞s]6XqhH7 ^%!Q3}:R׿S'MWB8n  x!S_BWQz:k«7 Q@K?@`-#ƐMl7=J9B~[o>h$̢6@/0R&oYۖ\޿z⵸.r$Q"0:xSO;~fIR6H# fu{ffqpc2_g}jhUƀLy_$HNLui@+`\"̑`9DZVGYqd0:aq%J <,Zt58=U5A Q)Sr1hժgN; 쌉P۸icWwwVnqL%"?ĥ i.#"%bl9df9OD& 0)6af87ߊZYuf2qHd#J RsC>:)@9 |6j""shc4\`$1c\N-"WJwW_:`D!  h~YRJ#z!|Z>}կ}upt.pb08kS3Ǟ?-Pe"p3"JU pŒSJe s^l~ {Ǟ=zBCsdFi`9ȬHT[mhS5Wʇ 3gJ nPrq=zdÆ5[68Nah0=aLHBO$HюQ\ cu X2V J<es̑f%KkDo~pttնm ݰ@!G8'E%fM_ ijΔC-8I>2J81&g-Nf\wH굥<_E={?G}@ E1& ڱ֒>{)iKZdCH5(Mr[7m\eMn6/58W8EqILkЭVwm !.V6U0΁#ÑK^_פ~:ťA+5|0`ڂ֠5ba ]2&pHD,.TT8ǿ[C*e'N1]FFE|!o#XмA4=vg1:Vsd($k1kNXʨ\< garAkwzf* ϥc$񅃈}i9PbI0- C wIW {NȴVo-}k8 !"#\ѩUH1~Ҋg&??~lvw $#H")x63q&b$uDq_/K-O`pP:>tSO>ZZqpKiulپs ckv0"ctpA d Bx33tR&IRu1ZYkdRzڤ\J|r\)^42dmgA.'>ŊF-D!'h 0ܻj`x7Sd2c ډy&87#@BOS??''*!b9RJ{n;>VH83vvDZ殽uԊـ"\ѣ<ů>x8 U kAt k],Ǿl2̞<~weđ$Ԣb<ª֗l6Y;eذ;ӕčZfiɒц19G<+Wi@=xv m5VH u2'v%O t!t T3Dr9@FV9ƚV]CZXBP@T;sI|<21FXdL"2"{^~0d QujlXj,V#fax{j6W1ZgZzSIUJ"D+)R˛-"sc6 +b5:yl=pCO',}W:ՄM 9ժW8h5N=ќ!tZ>r`lxtͥV\H_lqy%q㶃:W 7lo#9_O<L" IYCD[SgO9\{ZpW" &K2- f'1azA)O|<* bst֙syO~]`4M| ";J u2<_AƀaDjdd1mB!wƛ, #\2[WFzfbndI(tw>lt*]K^Zpc,  wFGF:٭5J+.N !ׁHB+8c NY&Is!!z[b 7`QG֚V3 LiD+ŢznQ>||``U!2z&'9kT+?<Pb!El) 2\.V쁃{{{ΠP~v}3jԂQj496~̤)llo 8{՛<'S''](`1(nP1d|㭷0=3K+wg nZCLĉw>1fa1J JͯbF/UJvo+:_K:rUZ?st+mv]ׯWًdt!Pcr9jEƞ>u|cͶ=!$~xZ)"r]gdNtLؽv]d zZid` *?>80Q !"`G31yfBM;zؐ٠5O0:s#D ~ZB ]lRN'batہA,dE>dXvE@΀Q|%T*es0A$, X Dめ@&6 mDqHvPk$+YC;ϝ,.4ؾ?5zh;F6:h98qࠪ$qQ#P6ȡ ׉"vkpޟ|v~n3EQo||+w;Q0[>r* qoZk(^e =S{®565laV^(Gz;X7M"ƚ*ǑZ4)WW@dW|4e"C c.=gWÌ+Ti B 뽜" @id@x#-Ӣ%k@ " }?|K_ٙ3D`dq\'b~Mk u_ .H S_ַN>ȄF#WmڲkawOrMG&Meq`te] Ƈ}ԙYZ,Wkǎ"pH*roR L$cfSOIB){*8DÇժ*ʄa_뺖^Iwz#aZH:uWo fŹ+4qivjjP(uwrJL?@k{oG$, wgBdM`H0!GO?1N`]2BWvͫWf~ \.9ȶ90mLŋwN۵]9@x~~hIy>O:i!Bd@["09%*n6+GzȦ'ʞNhc0Aɻ-Ym5 .Ȁ0!.hM$  a)KU- 8`/%~\&\Jm_F,' 2iU+{׾{ݿ~ 8HJB47I:z\-F9/~SH W&ZP&%'Zڂ!eQ$x&2N<}|BB|h4ZՌׯ_uY&dhR9pMW9^f`h4Ȇxĸ#Ė֭͛]54P$qM r$k7UjXy#Z20+{|盼NVR+x/M+s=_[-ܢ 002./A6Jh5olt|CCc\c>^u!)^;^Bn! ˄w2w"4FFw­ܷ3v)/,~_7?Z^t 2MuNqj뻾R we|B *@X=WjC>t8 a%ӧ㙙ëVUW DTp]?O_/>g琻f;_{JWIR+b2c9õ\SǧS *ȍ•F+!HWs܊N oݺuz$E"+]F^畒1xY9`j*=a_":JZljf3MSjxjōL+s6j||٦ogy GkiIbaEmΧ8Y)WZ}xO K߾i\>Q0r&ЎiJD.B8m[2B$)1Vw1ZI_jascqiۻ2|/@c1e,XmitiK[Q9:`d\x&|\`Pu\0ˀdS``tJ 0H%fٯS [4Y@Ze:\9ufp~ni!1cV\JJ;iعcۿ^r=. M=ԓjԛ"0FCц+bqn%VŞ bɧj=pQPג٦etk/ׂӓgz1SSJPF/<.&n=[, jmΉx'|66q|""vmhyK)^.h}7= %3@h??OSWF ]F|we3y_P@P] '_'zS14^ټs}ܿ|+_҆@#ӭ~J#./F8ɀњcRZ ](~?zԌEOHtF?ߴn͡ZK[5\i7Zt:$:?&@D~@fdbTqVUU6$,vZU:Hf%f0A L#"HLcF`4#,EXKX$a~w-eRͅc[);djJ$M3;5_Vw.$Afcd!''L38+kN5BHq""ka,$B~&(5xjS?#g&g>P $^qxW Y5Ee&mP.`nnn\[=jcV?|`wOWKA{pp% !*'y׷xE Q?_zY"o$-Adq.%2{fG`NAn{[0PY@y :%=訾m!}Gr@5 n[Fi$(Q9t 82J1UIڎʮDQI g( 86_ذ~}:xҌe;WhFbuHnUdq=臈\H(h5VlN*ͮb>Ƃ~˭ M͔ϜC ڇ;گ!9箝}zqוIlٴ9*`M4Dz@!̒e0 vh- NHD3 d0qd.+/,h)j%ujٴBOOip9XUkijrv$X@ w06`!b 4FPRQ;{ -vO ;Ż!"t21WY=o?w_ۄ}C;l!dQӏV3F)bRʮPn/7 'bqlI/VZOѩsahӌ*W :w Jqd%g\ŽbOBnzj2j56m &ƭ`1CLI_+HW? xm 5X"v +GǾ x_ҍXy۝.P,iXp@HkV#CQ_bg>5y r URpA:"1r-%2L)Rey;~\PzY[jseH[VsСsq2@.m`sg5& lmp0TFmjrq`BQ VbB/֚(jZm+V#s]!C0oMB|g~ 5`0O:8v/Nq~n,狮˂89 <9>Nz_?WmxpIAҬ{WfΕ&  0MՊˈ?ΡX"LhcD.g3Low玶ZMņ \H# tGr|Ye#Ye q!ve.hZ+!2gepgd` 4\ [jv*'K )gjPF! pGJJ0t:yR rYuby8]/5K!Eq(h A@8&Ȅ\谀ձ1#]k2+fn:{Āe&5@x@t# i8*#^w*] >8jPB=>37+$eH E?"[B(<׍Z-r2qbcR"zͪ.ٴa]W.SU_|Ò%*E"UkU9έ[l޾y 8=0M(e@30܃ Fo-r!d<ןtF0(nF__2BsfL}CEcWz]zu+:b 8uyO?8@̶m;DZ Zdph-ϾpHN (awg8k#1nOEj{H 3 *ݞ`U>{T9(VztdDQ|.Qr8SR./.,9AT%\^ "qY-M|Brur뭪1 AԂ٤Zk 򽡟CJCܨTB>{`3[sgO0Ā> 颙ӥ16J0Vqѓs@^^<0~jj 3HW!R'FFL蓥l" ӊ"Ĺ&+Kdaښ|I^E rۿJP.B5sgN_k*|Umxi/nE5 & @BqEDʂV%j(p}/pfA 7Қ~? Ӫ#XEMO* 4UҖ0D0 _!IRc2qܳ'gO8vʀD j'Ļw5qx/{Dpvv oIJRya8#Ŝ\UJk0Fc²6L{1k+Gx  U"C' R:e/|wuleut+ Ԁ: 's|k- 2R24Q)\.F[e$S=s |Ywe 2iPp>`T+^@)\O>SRwֱZ-޷`6Xwl>x_@3#q Yֆ/D^^\E(4D G+D'܊22f {C >3dް~ݮ];Zq3ã7lf!q8Zt< MD,c:tq߂.& _TR@,{@Wh0`A_Wz x5?|z[ $ڌz$HΎ"6&vvvcc1k"fc&F!iFնԒZjvdnz$TBUlVz{?^fVQ$@ u&˗~|;|,1(H0f4!+GEReQ46>Kj`hP@QH+87yA8b`Ƚwݽcw__h ƀ Z_m((5<`%^/3T0DxRt|ig/(#@XO>dwyhue̹$=?:<6FƘ |"֥!4 P'G. B7Cٷٸȼ ~p-vpl3<͕ DaW tPH ^FK}(`ӤT6YE*P\v '<_L `"EMgǍaxշpɠ /ڗG>$'(P22[ skq%{^Xo~{7~Qfjz=^_ڹ6 =ӥRA($gzfy+%VLdX$2yf&`fL@,qaJn lT $* KMy+k%?nvLҤ0{@RT/3auR@€2@"&&'-=}F""0(P K[@AB(cY’u(T?|/Ĝ0G2HkiBB (c}橇{qq&7=xX.Akopva S?:Ddy(.1,j$}lO|wUL2*M1c4Fo0nˀT#P[?W]^nj]0)T9+=THez 'Cc fٻBw=3@In91z76g͈3Kur@iI,5yul_~v;#.ϜcFcH>ىM=<\ZXZ3!YrDO?əJ"v$BxB(Ed7`km?Mf9A VsbED $!%*!PtwkG~~)9#$Zg! D\a{@P+L2#DD:qؤezSVǻW Μ^ȳ^{쎓nڵ3 U%,mJeko`8R4>6;[˯Z t5cf`}}yA_}c̘׏]?38$+""(O/N|uP*Rfu@dmoB)$I[hQ)> `Ldž|y84 !Ыvyfaa旻b0ZRӍF^o4mNrydxpffj/"sVs~'ٙT^[H=2 22Lvi7I3Z{@؉cKNjIIn ;_S*NYj4N/DR( vhJ*tm Xyq+FogQ8,= L.6W׳vlWֺRBTȲ|}m}Z֗BV|~X86XVfm l>!ib_o?N_5BK)XI,P2`@)TX_iήK W|}1I: q=g(xΉ0[zZf&JbZKr9甖L1bتb{4[' jz/DQJ@r@ TYkf4c`$P;p` %t%-1zֹ ,Z^Kݗ,7bB!3zщɡSJS\^kt)]Y]ܹkdEF^{ҖA޻SBaK/j]6L-|I|u7e@z!%&THk(C3: =3(]$B;o^9r 38P#+~ex_׈(KKKF+s۬])FV?6w֠Ri3DޔīogzDfY6Ӄ=m `7Hz@&6:綧%p+zMt[öb=#}@EpqvK͓ KZ268zS;Ɔ޿cZXʷ'ľ;v7ӷOؽ{VsW01жSv} ~y[V+{NB/=xE!CGJ#wM1幰A7 z.($TS=c1;$|#~eh(x,nF0 r}.\p2RkK d_1"bD Бs$51HQ2f!!TF|xڼ liv1,R{L("Bja`"},Ξf%3[g}ǡ.tZZ=/ͲV<ꍑXkR:eՖTJEv;nʕ(*IKFT'+d!R53'OW\;֙LYFo Z/(9W.KO 8~l7 T6+|@nh_V_L@f3no0^MrOwʀ 0 ` 6 %*:<}ųϽ|"$1֯~ep|| Oo>58Rz^"ɪ34EK㕷Sdxs|E@{ f׾./!@/#O>kXPYʾAG'̨ I/L[[i:RG{$n@n^ `dRj]u9GB 0I<{/Bo'9X^[G#Y WnL̨=wƆX4lt(J*VkJRz\>s/-O(O 2xOwo@]Znuލ+c^X#J++JU>; ̘f&&s?~ܜ9{~DQ,4$ NR:+ŕ ^z-.ru+9s{K822ĝ]S;D!@JTBnaeU鈰Q\j LNHqLj_vY6k0ۼ oul?D D,T:%$oW_aqq;^9zEAD +APIсpR"!1{qR:5YZ\888wN&EF;qɁmmS:ұI9ܐ3P;>b};R8 |&j[:+4V(;(Ցn<4?b9|/Ge^- iULK* <+mg]zN; 2 _3?_M3BAj[#Õ%2tp{2 :@vȁs >1H͚F'Q! ;rm$ISR(0B1M=c:FM; W:^aߞ}w,  bqtli\.˞Y{(f@pk~%Mû_` E8H&@ 1 B! :nݮuk8n/iZJ^1KQ }Ne)$B@tIp~MEX?z=_,Fg @)b effvnnn}w]kF5;;6ОAfP R*rAvs~,(h?ew~Mks_I)f@(@Ia10T5BH9@J{[e .m#HE3# YKڻnR|fx0pwq|moJ 끽+Oon2{3wK mt)'^#4)4#f"7M V:h,}wcͤ5>1>P(@`{7SzŻb(V;v\Y/7'=@ԥjMy8At|;ٻ{~8g#<'_:,,O乹|umҞ=tAlaq\;| 4Q}(&GA6eL"2C`r(yD! 4~>/ogqw{/8։eUi:_b}G~gVSVF -O62 k6IewL ,T\NJq{>.ͳ\qVޙgVu-w#;< QF"uT'(XXf+Y+z012$}9  I4MRAIA77*9`;Wϝ=vMLHJY{yemKZSZ5?هHD(TpU3B%jTRy󥯤 xa`VZYZ aeRKѮk>|;;Fmv괛3#Q^E`meuO)Id<^!m<1n|3sn6uVIYrx @vlD\괅+kJ&'v'-+n\o436+y1YZ1)Po?S,N\޷{׾=;{qzb|R~FPDBpa(N.2,¿xi /TkS29R{f/<__vZb^P 剥噙ɉJ͏ (R֒]n3vnЗP%݀=|+O(^8Whk$gp_mf4mTH}[.CC KgH)[^nzƒ 26{ڛ=f<{>ԣG8E Fqw[BBj2yRLii]㩬jTҤsⅅfA җZyˋN6{={wY!_S{(߱jwΒ#*:y=a_&DĞQD9g(e]Q__/J_7X@beR0uR^:3y:Vֳ,ݵkR3%B%ͦaQ٨)sf}?˲ s'q"7VR+AD)֚Q0 ]<,˄Z4IBh3444ZPz 0aaVWֺL'eZ7PR4n}A+ AX 3Ì A%傤HWx[ՕŹrQ![& p @8g[?o3kėׄc9!{uL r̎ŸcC!VCrY*B˜y(0Drjb<97;ggg=ϫΝ;?}q:;eﭖJ+n) x|V0^n!FF@+m P$;{bCŌ~mqBV948W+# B uvvvvϞ=RI묳FkQ(%Rd Ήqk?wȑ9@DTؽ{@T0&p6\"‡yneYfo~kȐqn};%r%%˳K=z10Y\γ4q| ARj2)|DHsZe AQ:yO=~|dtgf عsgɤNu&yB/?G~L$H!Б&'Ʋ,cێᆪǵtgvI p|@yсi 9xBӓ~\ZZqX jVyiuW{,uv HvW֊BZCZ_f=KDJWbw㮳<_5  kkufh^_XX+KDS8(1lލlgP)O5bd Μpb8hZقN !s犥ؤs:dWb%095Ryb3gwy/ A+( hSW!G?v RJ T+G#>x`dxNs,,,(%vIY)<'րLb/3Sv2Py {ڏSO>г]~ׇ # Ky8[h4Z hL.@31@nlo2=S;IJqǝ+ý{!  Qi ]J{y-~Z1<?Wv0CeA(ɥA<7ʙCyܮVN{;၁ Ξ=rP*N 6?ަo`w]ر/呑ѱsgP >nyЁCw "@yE~y|HW;͖ N)WB񆞟$Gs^+ vaq#Ÿ#sy^UP(xB <_4晧t#ZZj['NZ]]/JnӉg(e((I:p21ޅ%C14CDyVg|b٬Xʕ !0myeM7}E݂(2kGNyk~;%#1HVO܌uyڶ[wך?}vq|M|--~3㛱HtvgX^?8XE gF%?7^faS;5>::3?ӫy믶ťύ@A5SD%YW ,&VR#!9U 2#Hִ[͗_zqppPQJ3Os_}܅噅q.{ʘaQ.9rݎz `:˱ՆC!7{7f6{g9#2HfDPI!̬lwu04?u^c X-U;KDǕŽ~d1*|212OBfg^%dqqVafj5Ϟ=;k}Ry:XZ_]?zpp gO2SZm5\dQ]]j%ZY쮮Tk={j3+Nw<{vNYʲ9? n$mւhjA\mF+¢5,&iX(q0Sp'Ɠ3cJz_y)}cY}?:mD=c $ 3oZ-ֽu?`sfYXXʭR(*JDzsU^[KoXklZyOj={v>|0˲8I QX.U_bt8b`ym xZ `Ɔ+O1'iřNy,˃Jjw6z@q"sq猒=D{}k+łwоn)x6 $i1B BJyRi`a-T+}v76釞RD͖1&sKbd7?=/0H$' 1  fV$B7w1& MRzpw7! ȧ~>{D&MVV_|EJM =]{'ލc)0@JvelǗ8ڰցnqR,7M\RBAsK'N59&<:q"pjb_m?v%*~P`H,±щnS_o+-/wAX,qHKX]ijo4kIjIBX, >}:M0Vӓ'fGe.*h4 K Z;jݎӵjǸ`\e۝'O;KCcc]jZrդ@_!ewZp%k84{G'ϝ%j7+/\I۾;5Q:qk0,=z,1d JI,P<511\+yXN9KkvP Py&VB)#ɝr{B ZW!oͱe!)4›l-X*y5bwS4HP #O.,ͮWePL :A58R-,.8Nu :67MgnrPjwAzeJn5hQ; xȉzAj4Olh[ǝ$ "5qz~ydvfiA9ۙg>2:8=}197o}>04\ 0ӽ'|?h:v;v=;ګq2<4J.Zg31i)PL(B'u/7rnE"тj ҅bQkU0Y,/NϤY[}Y#] XpHOb7L گgΜڻ{Z) t$&q_.0Juϓwݽ/[ڋ|_gyqWC  Bw"xCD;o@JiDEH[A~O=xa`A{E~@*sBi`˫.^$@H) %8v鹁Ra_\ ǩX TݸcQI)؎V7Lq<tά8TYeY#P< 0M[[ (Wa NL }EDU;mTʹ|fA\zu(cwbm@SW6;_}񕰨qgs?x0Z{osPRi ߜ8O>w]{v/.->΃>w^f]"RJH%ZOy^042t[b/SD}$I&'' r66HRc 9I.dmcsb>[cTaeYI&7zZ*WEbA,EaAIJِ=8P%&w=wk/~rx33CҸ%zr,6z@90R2R J)|89P韚7;;~qiT(W,80=HfYZ\u@R(K@Ka H=f`X'`-"K q$j=X_Yw9uXX{) {jGϾp~>\o,<Ϲ,[VH26RcD׆c< ?!>kSҤ4Y(6~ }9)zya_8Ťs `Qv]`pǎ0! pruy2% @! $<$F]ѫ}vBT@`O"SO{=q g6@D4&߬Ω^Pą4cD$&d`Rꃇ$g!9AfZ55Y: )YH v;\YYq`C]Mvycf ,a2ڪs?axt0S*C^|pl~3 /q_8wRg?0824P.Fbqrbt DgN RqB!C80b1kkRVX.L_xW{%WZ1OZKbytx$IfS)7[W^y=ϲnۅw΋/{j5kOJo0 mn(  r0X'J#nsqj?PSR8GRzٛf+`pxoƙ #%)__xlM_p݌UFƒcj~+%l,?y? Ϭ;2Z-j$8eacT,mD/@APԞWi7[q5N !Zcݻ&^y}vvvē7~#jZy_-ԞbA{Wѹ O|(nCzrܲ#.&'"!jCl%yX*Xs39Fr..-S?z+% $prٓoyMoy啵cGJQLGFS'O\8n{}l^oDRY|`TXl6FYQ0:Vs8;Ёvbl:8TSJagj4cBߵsjuujٳ w߽?:u[G4y핗S@ Aw @%)3F MF$`?px5ڛmRV)6vb!{Axm^AɆ\A(9Qv$%"uy-"k"eb1@yns}wZ IZ /^V3`dm} /v#<4kʚ|]c#Yjri))"Vv31}f۝|ﮝ\>^ Uʥ^3s绝C;8TMvrRDYɃ^]=;iGO x}v>AH[D?g!UʒzltfV_>;1998T.MstJ!7xŗbPƱ][ecv?g__~wݍ3gY28ٴ±H;99>M|j˾W)- _:$YJY̑C%@JB)"*PIW*zh6D {/O8pC 43nrbW~ONNdq2" =s66^2(Bt "@t =uv{icT 3mz]g^]|$g-^ۊg68Ji79=bՕ7 w:402L}_ިk)a1MR&wv@>zk+ ?Z^cϿɧ>9<4կ~fԧ~f_EFhtt,[+Kvvvvpppz}W(ZZZ贖8zR-N~X,n;(s?9R12q…_Eq7u&﫪 QL rVK37,0rDκr4sTrf~iArR FO>~Н{Z^Y°V0==_۷o|j"6AY |-nۤ/=*qPxϨ }/qåՃ(?=lڥmW dD@DMr}=⼉J o|H\BTF ²UnxxDXꦧɶ/^^QiřiC.X3Ν_8f36b%-`yqqme-.~;Қy;@{1Y:"|r(X6Jb{AZ@(8DR]gZB|];h !<')C!"\:s^3ۅO施#7ML5?a(c`]~q/<4V~;8|7v0wk4l/w< t=2qp׾{Ls+% ~Ұ/@;e V{܅sO>V.fcD9BDv 7T\nGtJ)+ͅCwq(8*֤uƤQ! !üYQ}W2 לj\q ViqБ6R+?rCF{…Ω)Q Lb+}|W~ęɉcSViwRՀ+iT`sz|9w@Kk?l%- ų0P8}U`tWYs=YJ%.o<9+$K% 2H^DQhOI!عPHFŵ-^ x!Y]JiHu|`we@kC;,[\XˌVj_~'>ߨ///]8k׮Ņѱс$ڵXr)΃ye9*PluAJELDdURf5c u{KKf}|tV\)qȝwܹcx_M*'hh9&74=l03k)ΑťeE}?`mmlhhO TGFFA"8EkppPPJk%DZJ@3l3z+]űեj#w[Z$0TZwKۧgӟ2y.яNq⊢^׏#s2./{'{j&bF&g=$9 D$+%}8IO1[r`^0-' F)90TZ4[Ju6I( GUWAW˿w6&iԋ3sK( GZȸVRiZk@-TJ_k 9"GN.DBeY&͋:H+K]OBǝs%:3:0l'%ke&vwh-B!@dJC 4D(DQ$ig{5\.RF֥})' ,@F&Bb%JIڑv^/a!Kc(+$- ws׫oTR4M`yy%M;suumv" w^k8nob*5hٌAyjiSny&|)]+]TAj?=w/FEinCCVTfʊR,64Wy*%|A ,K;r;1M띓wWgKcd<*g;.%JW^RBTJ[_zD1/Zlvz1<+C!iI!YOy(Ic&J_,P>#,I%hsREA$$DD.. TlIr_}#KUXMS 9LD"d[`h Q0)%H`kwӾ\[[GݬJuZkYɀ{: aj{$`fXH NR!i8GB*R{:rDtƁ%q02<6sP@k^P9zͷOw' iiM_X ;5N'HvMM9b j} $@ΐVNRsV*R&[n @ZZQ^X}֟s(3Fͣo0JZ%3tKyZ:;B1[Oy\\u;"u "`|ڝ&(_o$pt{G nx!8 ]_BCTȢLfv,5:f`DdfpGɢ(̲|OD\S, r[0M{n{/k)POܳ};ӳ{^ž7)RF(JSv5~T(<.hg F& ~{ixAGY=\9A,ʑC llO/b$1&]+Br% PP;souie+ffɑ#<wzISvsdd0n]VȱcEr9Q%SoWk^vJtlv"#!$DkQ? +_z6?=G$$`G`("V]K2@U "1(9`TȄ li2jswMMV8Nq60V_'ꚐW$78V*Gyh`jNLԢ.#ou!coA!Z2f0,ߏF* 1&>O;v67~aaO{ʝcݤwFQyJAԪ5cR6=n9[u,Yrfpx';rO@heILJb#)8bfk_&z?VK)۬Z-G?~9,KUȻ=M71yzdIsIUPl+ iwע y2ݖ@b^?\{B L$rLnկ? <>G; ˮ3?cy.˗TeUfP0G4d[mV5靉"fcb'V]ŮfbbcmI#R[{`Bʻ?ˬBa|{9;~_xhnnŋgΜj˫ sJl[+dozf⥙0j&Hd .GAȑ5FH5:KIk߳.i4kMXhx>ONkN:9_o6qwoy=O~'QRI!%Y bW$7vލq]&>" ,]kab_6Yt$5==So}{O y>A$vX ^b^ .Wșա6PJ*Q&1/-T_dC#QJ[otԀQQ(F2 !OIčL6 }[ܣv岙W_y_Ѐ7JLۛFyGawxȶTuMYr<j΀G Ď'nXט J-og@0oL zJ 0y, tPv- TeHBqgmS_sֺfٮ/>}ɹ :vzffMXm2Ǐھ}&bk 9B -@k/? rBqv,J3{XB/'v(d$ UKKGVaV}`X/^\ ;RF:;\(^180op} }n{?{{n@Osc#F}`%Y?11$ ;,8+e!D_ ;L1<|m+qͻ?sH3z)p7_;֎  n2+]':pZ"洣2b,8Wר`,&~'Z'ԗH@ `cސ-H:n=C$P fp8/; fEz\6I BHϤ fde@Ih#s`kZZέM ֚ \.VEDiwЍƸ1n?lă;q A9A,/2===B!RcG?- {!w>Xq&Nbl.i!T3zNK'3 67Iժv*B(8 q KIky?%%" r"kM6 Íb_tkhdxPI3Fk0 "|"݉bb^%Bclz!|[w[/\Os!xRJ(VM3w%wz]vP\wX~x}ydo/_a;"" 23S"&JCV"PV(rE}ߓR 6Z``myG4::yJ^eR1V^B)MY ^'Q9O(:Rccml6 N[)% 00!!NZ, &`bNm6}''o=;^~|< N\6vC,ắ|~g!d`iwtleѡ'?(0P ]EcѰi !'Ԕ"ָy?Srw~7Ʒc?wyfS(db[g1:I4 [$GֱR1L6hwZ`|P ڭ$"!Df=영\ӪY3tb)7yss ?F}O‡kg#q ^u:^uaˇ9@aAΈמvPIۂuM@)DZ4έ;ŹK}B9B̏ i WJ9}K|fR<20[`0V:"qHزM(6jϾruY݉V*Ug .όN n*ѻm.zv%xzY-J.~- r;^JP*#||ߞ}sкn8 PB+Rҁ p.-3w7:~(AdRAI `qk5Fk k $1^ƻzs%]T~W :mLkm4rÇ@&R0BbśƯu11,A"%G(:Q#1& \ݖR# H+jIs햄ҿ~S ՅG~=J g}W›I2YκT;6o54ҏ&b %ϿoVW VQ{' { #W/mPu!6IW. "v;ܷo*]w$DINddue !GINRQkEMDJ ~,h;r9IG DFB&w| w#1ɷl?\‡|÷!}(5&2K(]]]:yB;q_ʧ>]ov$Ě)ZQhc6lGƁK9iRN[=y O\U Bj!fJWJ;6][W+ Z*z,A8uٙ ϓncD,8g)[: Li$PQZ@'GFAԪb1sn- b$*]5`k0 D2C}2ƬƟR~=yJ%RH%i=FԞ9"TV39Gd-(K4d~wbGͣ<<!]v)NaZ4g5iXJGwt: _3Op׽v6,v^.ZX _׮ZmYbJPHժ!(HJRR )3P͎1 gfZ(NleUklZ!"[o@oJ H.Pb!l7=L!]؎ Ѳk[Y)DIk!ܾ}Gff|_JՉH)XT lf=v;PIڀ l81lHS1+&:4:8Xmdf=_ 6F7*2|%ӴB)kEo33hGvf=,@=u̙s`\*z _֪=)Zp]ԻfXCC7bBP7DbrTv\^_&fYrWΉ@7ƏTzժenGw,ڤ@cI N~St:I;ؒOW@L@\T0_RI/ڽ B)LՕv%5H) nuOj_]$Id KT]GJ*î? 'z ]SSoqt} !*-بZմPNʕZuueam}NGBRMbD)ʀᇶL<[aёr6Bh2Laarc-6RNLN*O/%OI 'hdykhΰLqڝ?lݻg#"m^)K#q? P;hfZ 6I|m_}g_Ƒx{%Hmr6HJJ#RR$JJTcb%%3q)﭅4 Hܮ|c=S$:v-xnso6_|PT0 ư{ &I ҚI'#%@T/#@{%xS#{NwM R~nm8-`bjV;Ϝ#t$K!{LFF;k,%m%VjĹGZBj/g _bzvCp 9 +/ON6jMΝ; Z{Il1AL_ҧ J}vnʡV~z.rӫvU-x/i;0OL# _ޱcI@+تU[*GٳZ[ l-uQOqJB@JeJ jI9kA*j#:p($$q;ITl)J\*:?ʈWⓝ_nkJe[< @?ݽ{m6q|mqi-;z6}O {_ v/ЁcDb+wāK(b۔߹nx8Ȋ iiv"9yqѬ]ld}^ 0{{;I[km)/ )-AzW@X$( }ٱF.W^u0 {{ 4흶 &׶(gݱ88~|FcvV{1G~:FfLVSaONpΙo./ Pb[A`Lؕ %untZ/3| S$ k3)7`IpNOr}gܷrƼm[DFdUg !5J+K D:6?21s;v#}O Y͹"xoo2~ӓfΝ?i Y􅵶z u]?&¶,"2A&}*oloy`o/_tTR}W< L_M=b^8}Ef=fڝv[W^= /_2kjwRFsq~ax |x})L'Ex(apB)g #"Dk13 ĐX ,{Eyc3y"R^}q  '[h_;>z|,z*68G Te,B_?r?_Ņ˳gytfzY)N@2 A*Ԛ7N\G/yRJ!%[Z\;/S`M@D4;7Ӊ:kyZG TءH 4s GOdt7aNhq1QxS >Voow~J|ߖ˅ёҎýrg`nv^i6Wņ*k`]ewl A!X#P23EDAHZnvogݳmE_?uNe3?^~~2$! ~XQ;Ye fdZZwŅw@M>D-%~@DN z/4l$1#oIQ{A/72 '˅UXSDDe0uxy},dža䘒Iu E]:`=v@ $1 opY+,8vA`D% hԗv=}GUͦKȕ E؈Pa&a@:仉,96 iSn 'bsLމ&H@fPīblk` 1ɀ@V_3gf/4*gmӗAB9F/ rg5q׏yv 38PcZKF!$ska1HZz ]^}.s TR*uڞQ.={>FIKv`癅Kj-5.ߖ#' ?>s1"Fdi+a!P`K_s+qDpM ,InELIJ@e0ϗE_{A;T +,Nwg"dv42B-Y.(T= /zlV,sŞB6kF۳@ ǷO]x .\d4 X,/WDq$0\ne ^] I kn֑@ B )%H1AZFs sIč.I~~k_xyݒqwE{3ۿm̈́=!7%`d)rٌv#`#3---=#Nr_R;zmlhʱnwbַ-crHJA4Sϛq؁%re(:[MɧףD$&-1dLz  F6d@a\T-s/  tYۢJ LRHJK \:ҋI3KQ;Ƕ| CǴ1D^_L O̯,$"*O;U[ߵc.tj.Z]Gd 7f,/2;'%<wܷӪ6B&T3lFl,1*| fĈ${w~٭ĴzFY>AT(R3n#CR14 O %!miC`!j6J(-JFewgog.\x^i4:'oCñ@3߳gh_w /sυ`C\>/JyCiE6 wwK{˿̒#m,٥l6ՕCn>lT)`fkC}(9vj}+/Ntx{(ÌGg}[fc?G묵ցu+],E‹/0==;:TXg1n :-} 憏"lN$H:5W.Rw{-;//[, =Ty,PB62$QjvlENb&LBP Fr,ؔB@PBL{ٲ3`Q rS"Z@ ֓L;"G4dPZ$D—#ǽ@L\6sxe9@Z:{].@ =i8sE+1&J:l-~KWODBEO͚[G62(S##g8_(#b~f|ڭv>'A(= isy)ֽzd.쬵$X/2C LQ@Ȗ@J۶ !#aF`5( I;jp,P@ e:)9j"0(@Ѭ6guonzPۋ+KQ[PP&qk*FȀ-3O8o"ڒQBy JX׮aVZ:뜵6Irp@ Z"@륅(eG HireRSFRK!>sUO>|`CZet"H%9D<:KVuE9Bk^Pw^}ld]@e)!@FXy+>Gx(b)}rDXf3P=䊥* V3D"aZ|霟$3N\z٪ۨ ctPB s7qOfȑeP]﫷8pZI%QVo/J7x %rDh!mb` bfd8@%Nx)IIEԚT¯пgY :JLd).{mۣ_:I |kjQ3(R337,T*s1bd 9l__3v!L"DNUscVBJyZi 'U+$=K GRRJ~չan ZkQW" (%S;wig^YX7 h |⭷.=׬ ` Y.˖գC%ɳ)ݳ/l&mkL Z}SFp+i(e(͸ ƙ%UD\dc OJڮt8`QޤjIJ+rMK8lv^z|lSma[kH$(/n&ѳ~󼞞]bfj$xLVK-Rw;cUkSżZV+t~HQ8 /_'.(sZhBcQKe@ LHZfTk/77y奆-'3\҉IBiG=1Hʗ"#eavLQNCC׿_[ -VcB @$L;O!I|$dZ*SsR))$TQY`-:rNZ^6i#kcZ,5Kـ@dKRV_̮ݹ8>xѧziiq]zSw(V։JzϞ3co'N\T~Y!eȹ|[?NbSD9k5UT[]Mc1nyy{k 5oԡ>'cm,qhX˯=yhN7R=!LV+333SSSL"LD!HC?s;ΐ @ǔ6$g %PtR.:Kb { TxQI B>X9@'( @b" ZxWśon7N.JE~:f"gOn(DFDNjŞ=#7)0x趃;vn?}lm?l6sҥ7f Ba~eqA>Cg9ߵqu/s >9M֛9y]WF fK k'O@l^ۿyϽgOk)O=msJREjz U:Qf vO4dKՈ@4LsϞ۳k>%[)q($8,'V$%ICd7miy/,TjK#c{[H{P\KIyW^zW_KtllVZ֞҂%+tʉၾ/AFFsyIntV`JP7KӤ&I<ؑsr kX6`u6>g}^zXkKןߏGq^.-o@j Q uJԢ浏y\k-ޕ֜ \;mW!ajeltP(|o@9[3#{{G@B*ŠꍸY_% N^oq['Yb:u|C lu[Z٘7ͭDFso{iJȮy l3UbS(d(8OZ+ٱs֬{;}b$M.K`]C`@"xR{j S~\@bВ wyϯ:GqGv&̌  A[m4Po4m| П_.*5&=N\ まNC|~ܰjcy>-ԏ;ͷ/:qT)}޼<dp7& ĀBIњwNo[^jD|)JaMˍoiOwjN.54P, ?iVVfݷ'NK\x_O{-C}!b^Ͽl~L0-s6ȄTkL|sϼ)g \/ƙNVŝNɉ_F\}in-yﭻkZ,:J|*dGLprMـ]Q1Dkl=n4Z)[O2Gs)vbo#OՓ}J@7::vADQ) &I̎nnfS M$+$c1q֘rqmU+qA0^}ch[o5>\Ԩ~ -÷:91>^"B!V f-{ m'U3rBm۠m;iZ GR~{ XhN>K:q$T1ė~3{r9~ۿҿ}plPv0\٨2%b@)/j5 h4Fޥ{W$I(bkgBuqChl'@Ɔ~kb N ZN/,]8v|vlq-叛TҐ @!k&KljpZK>urVr_w v)z60_@ 2y{zrLtl֘$IR+j˰Qt/./K!,9!鵉"#"`g{]$,b_6g7xثGD1 hKȖ gO;\>;M1lS̅s X 8 $HLJTE a+0:]Q&qGi/뗊#S;v4:E8Bg2)Lb2y4DJi$-#""'^'^~XJ0m$"`q+@/Й"_>2?xPi !+AI!=TbhݻsdTR6-rܬ7ffffzK>s3/'&;>uキv[V.LWZ/,.DH,nrIC$+&r,B]Ϊ2-DCC#AZZX\:[KǟxfyL/^&2O @!QI!i-~'Mw}Jt%C}AICjILIX+[ͦԪUAF(1.;{r^./P2 }>/ Kv*JsU' L50#J]dB`v_IuDZ)Yc1`)H:@ສM6)_xG?PqVY|qo?)%k-J'R^g^'9? uN*#'0c ظXR d -t)cފy['go[ξ~򙗞E+vvc.8}A2u_/``h nok3)殕GR3 LScB!u'@(Wr>j)$@sK.P I b-DLV4Է}ׁ^^\zzFr+@;F$Pd11] (0 @P Fo۹@,`lj2,c^h Fk ##Z+=yo8JՕB!#H'qms1"tS r\-+ߜ?zܩ۶;o~)zYr8BJ!GS(H)ē?zݷcDŽ|A⭷zv}MLz]i=44eUl4Nujjs@@j)}S'@HVC9 i5ErnT=e\._{SO<[펧W9&Z "lr3z"W^<=88lOQ\;&vjB#M(J<)Rl>A/{ѐLʉuI!":HlL:=CJS q "dB`&SDPHɖf}G^'sO: :zZ'<䳯v$ߓJ.,.dGFF`c6a=4( vXءBDkjOS z-Aom:ӋEt( aA2kQ)Ij7ߛRjK:.4fN־nA"c#jI 2ᆵߟkklDVe~['ժ6W5H" TjIJPjx=ܱm3C46r[3KBH`J\@QJJ! Y@B&d=(?03@ٷm쳿]F+dl25,~ڸcVVJ$Y$fD(YkB"cKB8l9YysηXې~Z1F zZ 38nЮ)DTJMOO/͵۶qIgl&=lj\.W׫S';wcVVV;Qnv뽝i 30]!EIs,!hH,ȠAXyQDq (r״ 07;zgg*U{~iqeltg kPIa --V/93V_,y~e h9I ˍFCi JA.a sY_)Қ3YFY S]J V)(9_J'Mxu+0Z7_ d)P (|2VynΈR1_Z" kZD0l.V^~8n_pabb$4nEy ܦ~0#$6Q+i$b͏@rvL)=*5[/[%F>hs0MM3GQS:mR<385?|'_lT[11+g_}w7sr6<)nyS ʑ? 5K)Qv=+Ɵ70=:`% ,S'꘨%f̒ C}qd|DdL҉*0_tBua]1Hʥ fhP0/~atО]<)? X2O<+/X;vuJVE ApAd![drӳs—b?yzס/ds8[6 [s2FV>ƆΜx\d!Q)(@"vȯtx >5@\&X`ӴW&&/M v?0ĜވȑPrdl|yziiiݓΝ{C]j RBG NҵK,ٓQYCr|B:?iT%2j /yWDF⢩= /Zxwi6rKG_|}{]<^SZ,\bم[lr{je;v([.<%@fr)紫_ln\?jg1$,K%(!7Ԕ!V0-a\"71Zn.G5 ߷k n`cG9?\{w|o BeJL)鋮e:nc* -ҧ߾X >v^3 :Ev055_ \DEtE lPP={ s#*u$g}Tΐ-~.$E%EѬ[*ԁ2 p@W"[nԻ?g .6#c|S/-Ggf<]FչȽ\,xC@R"@r`A 'fCz :"fb"\{9KyBsVyJ' ;ab?tK.}݉ڝV!!Ps1sJa೿@ ?@)#g]S^__g}/z;vX^ٷo_$۷mCRSQDs&Zʦ$ncJJ1l)vPH3Y2Y\xhh󼵐3?{Ϲ]|ӀQb!0厾$ƷuzLbRoX"Cv3?7VU-ӟfv̏YTڭw}0߱su__gsw377ԙNLln~xs{3:BmZMigbl&/Ԫp{<=\CCV* 3VkhhRj8}8DO@E,VVUZITG:Kyc@JSZC NMM5ͷ:Qu#W^9';_'M횸٬{uDJyǮmuGd}!Qoy" zff JOE "jԹٔ{<#Rp[|]ܾ}4mb"k# (ET Ɩf"ƱYR``bBbFR'IJ= gugH&Q}_sl!wݺ|2I(f~}+lN$ECBZ]//6k8j//oۨD 8IbN TZ`kZZ"=ݲҞ3ƒ@,u;"v I"|Eq_9›Y`/H5?K͘!2Ql"ܥss YYw5]U_@r3gN4?P,WR>(PTorpBgaa<|СCZ#G^ko:~Wh;s0q8w;$1]+R*0XB1RIFHk EvPf\/w}biǷVNlϞ46>\yGO윚_X8}괳Q9 oJ3wy[gyem7;5596R6WILE)a=gҔ s&xuZqe6Ԥn 4} H0jﺮ\7xfµ- 2XL_΋o\AԖkˍJ.v IZc97*zO|g-9111L;ܮ;i`Ykf%0ʓ~g<_3BYk#V*ob%FD>R ?JcP&'AU90$.19cPZvc33-nt,AbHBHϖ9Q_4I岕KvfB"gkZץx# 5J6JddJ#ۆS'8}LAJ&5|a*PӞ޵k׳>}K_ڷgjyiyyiܹOg?72r"r34oxTo7N:SjY/3R?f8x3_={VTv)*rdMIš!nάM ,5BoTnkxBb<h'J$sJݨ|dbFm n,^F+jRZ;q]L0Io⾃WqDKcA9JN&e@ baJi#vv%ˬPۀ3sGR4}k@):Z`pڨ͗e!Bہ4fy뭷=EDQ%I4Hki^8zl ۘLadΑPh =RmϮ_ۿ!}ml3I:f"uw{qhָ+QҟfbN߁>q#X" bel4rj5Wb>_֥K{{KŞgYzY{clϋ%6eo8RxntlFLdF?2<Ʉ Ko699AZ;~HXڹsgooӧGGGrG8g~\-[m(=ϛSr+.dd.]" N+|?_dľ&& ˙#wuܩ 'A k8?ո Au͸1")$ VXsťzU~L,/ڭ~XZZY[[():w \jf!pHi !NW3XCq"VJ`W qfXM15{[ݳZ[J 2R ',K͖2===b N-PRj?~x'޳BOO c-: O<7h$=&&xnui@ݽ{Ą@+ *.8syfhph``l...>v~ ©ӧ_}?o.%:}}{:#GIRZݽ{ }@ݭ6s쬇k1%:ld8sl/?_΄lQ+:B`S߮UIkm-πߗ{|| av%}%A[ħ/@`R ťjS[iw›)3p5n;o۶oVW+߿o_ `7QwӧZڧ?-7Tko=nuGSApٙى##C{j4QY^ZZ~r(RU? A6I8IP ,etJPJ_}&I+cJ : ٺ;ɞķO<ɝ{OuZgM:6ⱗrgxt0 ;oڕ;qԶ?{du޾U^R@ R)R&)ۄe{,kt8&z;f#˄U -KD vTj˷/3+ $v@ 2{w9ߩc,J)IT' PdZl"cO}O|OYM;^9~v/j=vE]/Z{Ib,cM F֚=Ch zҸQeۇVM>"`n,3Fkc8"C`\ǑrpfW1cw>DhA3ƷȨ!t>&\_\Y]}Pjɩcw;V=n]doYt vA]P<bҸog"P?䧞V&77^ܗKWgg7ׯܸ^'u7Na.4]/~Ɵl6;ۛf6^M X" hΐ1Ǒ]!Ҭ8r08$aT?ǁ [6fFaJ~7ҍrj hf0fCG2c GNw'Ƹc'*#D\)EAK #K@1$KȘ5$ ֆy,KnP ࠌՈZk5c<Z'i-/\NEu va$P,jrLEq)bhwz7ff&'}? oװ J)6Ѱ0U\͛ +҉ɹ_'iv~ d\GDX3Ppq+`Dk Y k $p$q RZ )`njfva3h_?3;Jȃ3L͏M g^Y|D`ncT_kO jN!jY:Xv1_s iƈ8 ={n1H!;77wxq{D~erR)3gggBV{nvy0r'Ohk{0-9sfzzz8>>>.s]W^{ggg'&(4MyÇ7nl=vco|`:|o_~i8U++OL+7W>9:ʥrRJFiƑVE0Y$s&3K3yy.d4w- YW\^FQ87?:1mb} Kt+ֺ7e8"2*d4 X2˘8?$ʇ={VHQrj Tet3nd ԘxG?2A C4'l}zjbrrYԋt?'˗/;[}K!?{i~Wʕ{Ǧo,-5zmηM<#? ܸ#ˎtG/|G?0y֦V:uȑ#~/}4}'}1u/j뫫koqqcYj^ >#?v\zb'zA| [A Y$k#`gu]pDPqkv#@Rkx#Rv0za?\YYi6/\F_7P=;/m_5C6|n+GV9TyYnEhB`ຮ'v45p&8w8{9,oӟİr/8i9gBoiLgjXL5A6c[u9;rev3!C0iƅpP <ȵd 1IF' pd5k[;[`ZR!%hT4hӲs}ϗa4BxH (i:daHv\^kh)Fb}ݚx  ֒N/ !)=S-;,:ٙ?ï_f/^|C>ׯ] [$Hr%cb(r?MMO|_ysC;qG(҉N-ڮ<% DBC %_3'Fi ąps#KϣafJkx1Aomo.//=Y#/\/2; #y!Rlսz p I}wE"c9/'@Y ϝ;Dt1>]4+JgvzZ)E%bV=7pHP,w/}R'羐e*M֎z'pu=)D.{㍋~'~?QY4RZ8gg5.\xWy晧zT*Jv''fgfgg9s}\׻zr\fY C0Ҏ@4MGBP!- <(6r`8V Ms`%"uNȦdr*ȲX'>~Kk]#87{oh0y/fqq!˲?'>;19~}6Y\A4."sz\*d^[BQ؝dB <\r!$|G::OvGʴOH4MpHT2b;c c'˥ƍJ&kz7gfN>?=[8=;+W.}s619w>>5o>pڥ=tFdiݼ&:#@ fm1ŠbD c 2Cl61[ipէ@)2W M$UdJ ff'n5RtB2-{{c`B~J R#`\&:891Y&'9E奕ÇW._=vG~&oqP(8~|x(/|&IllomWcGvDLZm0| _xRkkk_Z\sY,}l[Y^Y]{ɧ*1qKqw̡C l68> ,Yw;y5R4Mǵv0tBqJY$,ս&i _AR_8t7-+.h;;l}^ ݹ?17?555 VV>k'$i~!_0Zk!Qȴ< @?N{4t/7>vrWAJX.Rff~wW_ϴt8B^XPZc30s`c샾?:+i \#*21ZX}/fZ 8i-us'ӄBqX,UK'If* Fv}fT#9+8I <3:q7Uɇ(-eA"#- N,TaOLXgIxjb#ޗ#[O=yp"(Ν~`rJ. ă~⁡  DJ[pd3OrҖ1aaԭ$XƈYWcNW @߽)Dt=,KV'Yml!ɚN3^"0J6")goX0;\'m>幯}?.1rA @pqȑͭK7(q{h~1kkJٹ|.'LZ} _n6 ` /=3>L6sⅵGytnnp>qfk;|cT%/#'^>ONˑ33Aqj761FAnUޑ}i95$Cp9Cc1!/l-icC0f3`Ae}Z6$SkQ7vus(RW]~cm$wbC*Ka/域o+ 8V'8)(&"4,c=RdDd ;mA{Ţ3'*isOdϟ;SY611h4$)J@ )g((AF4"c ,1 ~Wd {.= ,+?sĩkMڂ밟w\Ջx\}j"b2ҙvT5+4]wl|Tf[:6Z r>c"D P4jyp!@hC`9g0VD.o;H0!J%ވ6N9ٟ3K%(QtÞ,õNS2Z8$FdRJRI2䐌jy" \ig+S2ju:*߷2?rHo"fbISSÌRɛkIGqTȿ~twnԾ->Xw oz<%B WkT!767GYuY@ˀ\,@R8F+9|\8BL2ڭozzGyDJl6>}R)=ѣGʕrsA?R)SO=Yν9?r;55^\<أZ=MST׭V*GOV+uUăsVkuUJo^#Cmpop[/{{J2_Ȃ^.Xf#%i蹎Q C?TD"ԓgys#&ְyzz׾K z]mK%[CfOT2m)J(ZaI !YDDUFٛR *n mi ;>36sIzu4U+2#WJI<)|Pz[N`%nsٝ[JE)j\3"2.YʔbBc [pE.T]bzKw'p7j>Dq=Q'TmM$b8"#ƙʀfț͛7}1DB`L359yÇ~\Y^\LOM/K/EaxzH(^tjI)U*o\6b)Q%BN, M`3AOowF). b!pHYY;h bR-7McTT;s $ }) ^4=0}8 6U6ۣߋy񑴸Ҥ4hqĀaŅ(9,c!q筥׷I s%B 9eLƉz7BÁd^SڎOy)8L|!Cks6%I@_ aG@#=HJL0HSB9csp-ow/޾54޺`n ff'L0A*F 4fv],Yf9Иؒ(rJZ.8AU0ehi \>qT;2i?=eAwt\dz?4.}h)08jNgK1@2@癅Zԉӳss~!? $m50B;~ I)ζgϞ}z޵k66G{X,ϟ;_*Ǐ(n7fjjP(ʛ\?SLE`kj_\paȲL+8?vph4vVU*j%K3Xǝn@^w֮ݲctқ [84r_0qBLJz^k|[a ?Re,ik>5 :=ˁ`i2)cdTJR?Tǫ`@-qurGO:]ѿ׿|䉇\~cV'ƷjEW.`˅"J3Rf nqh/>{Z(L*)ܳF_|)>'w2J(,sHz]udP̬,զr||>>IMzpyka7KAi_~Zk#Onyy˭ol5zCύO:yJxc 驩ZRf}c= Çzg$^[[pǏ~ދ/GԸzN2&3Jii)a;p0 ww=9 ofdD75ę .3Frܗjmɜ|cO'6WsfjO_q捩G=+b߭{9j}qzAEAARHUVgeez>S٩GGJsSh5IaT{/6J"4({X@8o`>i$gl7~yxa9M:UD~c˿۲;a#bM&@)=a⎗_hk'͍-)GΎjQo| rȠ9q=raqƍ5X^ɀ`ɌJ hDqCzt;NNo}QY3h y\-\i1&i;XZ t\,KS:W\R8 [*Kfg)M z(  `9Q:o ¨b!e0ƉH)%)c!%M FkB֘?GE)Y 1$"H_\*30d1qu"jZumfRkrD.HiKD RqbѶ@We'Ӈ7]3ּ??Of1>VOӴZ- |+l nwZp]oʷٹGy?~a>?SjkZqWzI_V(D\y2_i>g;55[tر#G=XzWn9H\3&9-0;${X?CbkPi ޣd8cZ)!dҵ+3z'A,SX4;+k(Q8Z_Rk?wv1356FD?rOqgz46|fV-(K'@ T5B%zriu|jN|n7~/]*|=>RE"YeY]!A5?bR'QL ]>+8;8rx RHbˍF^vp4ٹIk±,5RC]P АSІ]1;lj Př˗ǽr.:ׯ/]?jMYʒ0&^@h8#tgFk;Ra8 cpc}-?"1^[[kz28Xk-YD[Ӈ۩BОwܱ%- $irijQsdY$@ 3 Q!1m\0{}w6,YDeObB53W r,5Q4A4ZD^8B~v ( Y*^mBj;',ev9\ZkP)Q9c`1ZGq DFn|hlPm5dk;S d?/b~ARyz1_?]e᠇33c"ۭV.!AV|'N8}z vuY!RTk'NL^zt諅Bُ=+ͥ4=sSN ˗/rt7V&LOUj}܅f?>>9贕R #՟;O%% V 挧6]! 2g>~XfR0'= \0 7nsn!77[(V7;+H !X7O~~H &Y`GF5@k.'0$`f#x!;H7c$x޸7~.=wdKq΅tKh pɥ WZrٞ+K٩JS*d Y!Zo|JR`,!;xvGǵ̿sZ/l F”ի'Iur!s43 Jr!|>U;7_ռҩ1&IryCTF#"dYӢ{aFH<!wyo؇߃rZ$V*_Ij0R%C#0Ǝ?f*S`bQ<̅~Ds 9_p'=WQgk/h1}DDZƘRiRJι1FmB9*+~`:z/1ڏ%ƀ%I=Eg7gDZ6Fp8g)q"kqǑҙ8 u8RJ{'n; 2,殯,6..z=Kds9ϕAn yGϦiz=Z*,k67'ggggfr //\|̙3ǎ.\ ɥK[ s3YnSO=1کV ?q$|>Iw,V+t6B6˫bqzvꓟx\KlL1'ƋLr%KTJ2`nM%C AdY}k8g1XX^]ˬv$4=`bKY&SwS? ?d:^{C]7=59sҵs_p`痖CnEaf D hFTp$@"`;V|7"Du"@sFw{@ jY@. Xq90}U/C>(skOLMN*<W+RZ>0Dž|•^|izz~jW_{{Y4HjDKo] qw[ʔ:\4-l w%ZՊ1 1: EtTgL'>MF_C?|~W\Qnu~չ?ӟFvYK6l B YCJeHAoĮ@omWro|hm%d  "q'Og1B}Oώ.B3)ʸ =`atj{Z,4Mg e&Va8.ur+/{XX)Wrbr'@$ %p)rN+|/dѡwewxWjVo}X"~g%h29B'^{m{ V3GL/;xx>鍋22:=arJy9(7K@GJƘ]bkfx8%"9u!$Y x9h0q8c6w)~?aK@W;0vO ѭw~}*@MΗ\`0H\Y5&ܓA`\ҥ :S~lpq־5 >[g=c A ycMtbu#u= ۛ3ӓJʸyd0 9cUQFd*%oy-3ƚn%"'(FQb^Xk ,Zb EqFNϐU*UDnV^;Nכ25d)qNPt49(֞jo8:\b͵OdW6dq2hsyc2~ޒMm,0"$3ZsəpeTF$\Klx> "Db#1Kta>WO~KңϜVۛW\([s,q =J!c#_j2&Fw]8Rީb kixRd~8"Bc@R)}VHigi357] 1 HØ IdޗJEkDpHq N)q6 $tғ'N~%cc֠2++,( T*sGLz"yK[3]BFYI)CLϘC- Z3?tTiʹ3F\pe u=CvyAAErSpII.n/ $ nO!B^)v;pya \A S3)P_~>Ҋ."O9Rh|@`kk7gggʥN&+RZq k( -;.$I83ƹ%077 `gg+\qA|ua#"c\Ufkc3asciouxKY%85z=Dr"XܚZO~Сs\rzi{?9VWW(=8RJM8}޸7~`1pKlQd C^W^ZRy>VzDaߟdRd-偻G3r8"; УnXKڒVyWsla7%adJܹ7JZ5/LNM/~ƿs/Xgn܈f;n}G`aT%*HȲڄhЯ&w7fZ,sq4NӮe*Zj3ϻ}ؽLh jeZ(V ,ӌ 2}RDd@>ll,-?g< "g|t%SKUHJ^RfmΐK;8(\zdIlmAR.MN )Z^AFSB^V3# e:RGiJv-ۃ[0Z2ڂ]MC ] ',˔օ|7Z'I: ]ߑ6[]q||ggKkSʮ)JG0qZ\Ζ0$,K2! rSA.wsuc}c\)FcqZhZڲ| $nvBʤ&3f4Mbl \QnƜQU}dig9|?rFa(#%p!ZǔC_y ,=Vn,Y=R0R.syi1VV"D<㫯rg}PZywҥ'Oj0޼sرO{իWG>}S[[[FZ'3.]祗_n$Ib fzzb֎/s '1A5W*+74Z=[\w!b^^,"[?rE# c8¿nLđ ݖ=ɚ\O ;XOz^a8z7tpte  (dޜ-Vp.8g2Ƈ˯1fccxں rc36ixr%شe,! P9GY8O6^1G,I1*8`2C,sO+R Ω.]#=thX.'%,rCҠP” + GY<|gύ/4K~YJF3Q\4 q,j8֤̠B8y 0ҥN` QZ*VwVX( 5Z~&3! )I{:oY^?8qv:dnTc[ ɹ#8QVgYy)' k\.k2,. 9cTڥrQ # JՁ:UC_rU!Zc9oӛKsa?>ǟ~ڵ_3O?_W9>W(DhB Øct…@d`-_տ={ŵ .%qNFe9di{Pwi7s"~k,Ԩ7E+,xQJ j8_KҨCJp )|}koMJA.~qG'E&@:,eP{E+BѰh!Angش?DAG=ju6Jkȏ,p  0z;H1KKY\r57>Sd`]zQc‰6}AG]A nX68ݶ4GD"q! D)7ZʅÌ^8v''u;]pgYe֒HVkM\xOU{"k83* $+7+$QGeI?D]:FJ)WLK? 9;80WWidq+Fӻ 61a fʸ:~TXͥͭrƱ#eX^YY)+pqۭn/+B!+phlQ80y)EDZprYz9o6^ovfu]c)N;119cm5Bvpll\p (lnl@^av{Wku/(cVWוsss5a8qpX+- S 2m-LfDG!f(6I<ܑ*(Hp$/]qa ɀ>Rl}猏}Wl7BN Q\9?7T^opJemmccm:3dM\>{"o~[qm+,c`s{/~ϵƗ]=2u\ttϿd:`v$cniэ >^a@VAqp!u)y`/tňn=*{%ɟ<_<7yMĬ!0acg*X}^*B2`RQ)nS1Z)yq3ṎABh]z?`T0DFhG,w5LBі"YͲR$ٳ6Q>}߾z%W0'SX\yߺzm:7=6?f5j@$Yazhd@R,@+yqv*Iv%dʘsxmqqk,YkRvY8 88^ Nyf4Fk5*y0˴"SIbePk#j: 3k䜴N;=ӋNm743'O>tZ1$K HtDň璈eGq34&鶶k4uf=/' !KهҘ;FR^*㍏O2AR,Ӝrd1 4Ms{/JǍ8 Cjq cJic 2.ZYm f2=0)0I RɺhscZZg^:kv37?W;;;Yf{c!0HY\ (6rІT! k@iB1&S9D Q eQjp8ժ["&~_(NN3Ê)CV_eIRrA= "%r0 21?8z P(`skKJu}KN\\ ]+EXp]Wrn'V{I>DaoE䡅~OdWʒ$ ]T*vŹ( fYf Ƙ|? Z 0a/I)zD~!#JBl:~8PIhBVv;^w qΌ1߳ 7{ `q{ޭUgɿtᅗ.Ĺ,7r~"d.]WoJg>C!HsXv^$&BƸ Z3{?/ٿ ۡ<+W4L͕n#־[zb"\pw"2;+ƏC~aѺtq&:+]T';uȓ!%ydyO#:)V5wqemn&$q~(~[DxoKł Ӗ.i-b47l|W '/..,,YRc%X`%C)whnDP*cGzI*K/^S'OiIaMNL}ĉƘ4M]>77p4m4kkz0Z_tc~~arbr||^2fYv;l0̴1Ѻ:vxs!^[w^$/9xNMMz74ݭzBm'S)&D4Z /oު-`Z*cwڟo} u{aopk3 beqoε+}>G}T)y @*\# @'EϷ^.+Wq!-oiN>|/ _ނ,*"c{be"J<БFtqd"U%AFYzG:Z+{zfD Ҝ dGIM , $v8G`46(r$VqWw3Mo/깕tc.90 FcD@LXmtFtnmll//m+͍Kg;qhqjPqV[%!Acr[( +gT 5 kG kM1tLI.#*M3p#{a!BF~f l7$B˯8 y LJ\A:DYlF!VqDPZ1β,ΊS껎n1nZY|K1FaqP0*W(r V qԩcN4جDDc c{>t*B| ׌N鸤mRLi"c\p@yw*y: F(JDMyN1ErHfv(W_<55^Vӄ~u0H7U&^+b^8c|gg 2"0/C&8R()lloюƴ# V ۵{%٥4$HTvg8L(唊SoQX`mmmj!cR2tPBhKV+=51ffkӞYL*e\VL[x[k3d-j8ySh.X^w RjhiY\}s'Ii4# znQJw:]D rSSru(\ $I9JqWHtWWW_|ĉǎ|m+ZSS˔ij0[j@\CĝǏ( K# Q=C̈"J֐Ȁqp8&|;2)T oQ[3k`2㳁_[_}nqk7"BccJ܅snM/A؃&3Xm1!oA[+Yu4@ ]"qcS! K0~W,;'9h~Vuɉɩ4MWVn<#33jϟ?w¡ǎ4Kwvlk{[)XiZ8bxrF܈./ \h~~ɓR)IֺVZpQ(G{]\puˑ{N1sG# ݴۜEκq!B(V*@z?#GK/ſ?3foFqګ:;5=ƕ7Lե/ wv~_ʕׯ-3?o~~#Ġ*]@3 htRJ̤)M饽g7鸯9D`bڗ7[o `PJkޗWqr!<"s&.ǟd͙iZ;4jF+%^QZkFե\0: ۳r?1*Ɛ` 8ηWW7:jRyyԡc3OtIhr2 -񓧝Je3F^!gA2%~~hN`1a-2V(mȧ&K,3WɠZu\,Q;t{j0H,aDzuccz%qZ//MNNH9wẐ䉓ooאָoÇ7k?pwUB]W7Wwģʟ\~):C(Zk 1,d|˧}liB-uDH1Nq\Y \O[[=T,滽0tN8Wn̖ W(I?zU2[t8AΗRLMNr\J7nlm>}T*,=WHTzR:¦Fx}zzX,FQfi9BDV+SY.b._ 4޸xq8aFZC ґj%RN1X*9\b(60ɩ4ssƨVk;dbpAF{ 7-. vKf^C(ٞU{rwAs_괓/i9 VƹMh|<'>O}#i U@ Y #DjPX7XRpIZYK:K# ,"RB& X$hc{{ݔRF4JRtCto{p}}{0{s/>#)thFv4 {Q= 6@ءk/ze565zA[lDqT(ev]lx'(8wק&kE4!- ,L7 1?Y}lq:*K5#y_M=b9"rig$DXc 4N,ݼhwRM?MD!" `a 4`qTJllcbƍAo}$U#"玜tG?1̹.ϗJ!x: ;#>q1N28C&DN)p&V&5,Ll9|,n: =S:j -(rݞbe\D8kg퇤R&cTefK"gq49aDad;\eIJHW$t4K32Z#r)XF.2 o%RU˾W(o缙ΘYƘ skhg*) CZ X UXvZ+樂[W>ϔU!LYdn=RV\zȑ#r槎:F^? , rV*W^) s0VVG=Ӌ+~|||qqa~~&vw4HXc( n7.G}ѣQYClQu{]52KjRr0H!j iWW+Z>_qc`=vԱGs6zN?>5=e VDm3S |X Jٽ&w;3L*Kx|kN??/k3V]9w>5:'¥_~9ȋ/MNLM̾/w۝W]ׂU% O}jbbrym9qO~IQ;鞯o%؞vczKD U||azO|g׋E'KBRfZaX(HJ2 Aemu9DpڨL a!_2Fl(c-1Lr 8H.,_ bu|89y1/7x hn3khm2ooP)-9٠{'?}MmM/sʍk,}S}1+Fjgp{KFQ% eaB)HSFNw@4DRc(#ժf"< # 8ir cO 0Gyv!6`n<c莬$c s1Eܼ|}0H' bN9c&%WEFr7/`?xoAi XY=<_r(^{UEZ8ΨqFm߄\/WQf K``<3+k#\l ,gBTi DW~͎E\72s&X}']H43\Yopm` ry;CK,g1XQn.K:)@ ښ[n 6 tMou(+}݄cb@J)T6JlDLz)3f{gd *A ^ml 텮cfgUFt\4&*\.L1Z((KLV4V}i5jђ䓥q"J5MSϕ4ެʌ\#B߿zj}l|vvvq6ss}Mpphq=/$$˲^B\nәz 0GK%kM5 899! *y `@ AJ !cGEoΞ7e٭6" ve./OQj8s@skH~/;BF  kkk+7PDp7vSs'#ol7{q,@D w]v'Kg i-D|e@J֨j98@Q'I\93A\ iA#:Q!iz !4~h=DΘvHGZcڐ|!]il|T)377 dFQy))!ziFhG6=%:zs#VgK/-f5w"2""cT,7^׃Z;ȲܽyޔU 6pMԐ3 GYicb!F]q1&}wy?u9efeUW5 !}Q|}~93ǂba{A0#(@Ȑ1>M>xoW D!0934Te\ۯfojpX*u|@L-DƑ tXaN80@s2"bD۴]dVKUz݄&o%8Wmn: G`q LC^Y o4Pz=k=rr/]T*#c[[.法3loJr(@J8ƌ9sJ'\/# r rDRVʵZ5nmMud&ÇTRյU=?JI)\>Nյ k lmmEQHSJT,TizA8'OYc6[Ͷ3jΠ>xؑ4MVWW֖G*J&뵭k׮@6w{cca~z8` 8gu K5òȁ"\  "drpSV|鷿uf QĽ+lOLX s5jns.֓3|5Zl!Vϕ^7wcayeimm}bn %M`67D=HYTt6$ёW_;k.Ah8Ԇ,9458~@66F+A.3@ \Jkl8q#6(\]NgLJL@:k` sQ,3D:N~bXn0ib| XސL--n.,7.ZoM$DsSݺMc;V|OBC&R@gֻN롧>? #dxQmW8 9N3876 48JA|49 vbX<9im"h]i e 8v<)EǠ$h7_bIDd!E"r?زj<37޸vcf>笳`E64;*au^Krnt %9"14C&MGRpH mGD笵 pwX3ilU`➏oR!zmL+/w:d|@1MY;[3$`7$BeJA֎'IhFFq:HG(\\^=%<  1U*܂q"X5y* ?ϲ\X(UJ"֑+ R$s Mƙu:Wu~otJ6rٙKW<{NJ R pyyRJ%T[׮](2nD[ o3{3w@C>fI ),TJ 5R)ҼH 8Gc\=ԩى}"޸|j;tyޥv'N{oJKׯ[ɩViwlckXm\rqe@![[Yx쾙~(J; ) .kPIoN43#GOoJ!AS͍7VLqyeBX`D@Dر9} m)/\<}lhyٌv}QU(Kh'_:9Tv'{'qXK@19?@!pJR*\9PJ:pЉ#Alzj!H*8234nLĶ 090 _ҟ^]mD@dɐйFidˏNkh=0 EMaH`0g T(kx~q)UPch #GiłVJ)ϓRu R2)}@J!g !cp[L7 ܰ0BB&ZS.n&zlYlPRe١Op+OE\JHF'Nl2~UH\>jcrm2]wۗv?jɸ"hnlmϞ[t@ik.X]"4:i_wWfJEo) 8b>Y܎r ov${;Zق>8ĵ=}uucLv;WvZ@+"@8gh̶ۙ6 chp1Dv;?ԧ;2a>t'C}w#а Y:p=b=Bj$mqP^0N;,8{\RǴ{t}+󍥵R ;`}soϝ;;;=Y, BR,.-]xX,OLDl5fZV+A8PJq$&4M1LVHСÎ IRgi}cKk04wcȝ:rksL.ʳ3F}GJwWVW_}5 0j6ӧ2AoGiJ{m۟`7/p3%n[^j_[zm@ :ΒuCʥ|x!܀Y2.#ٱS<~ju|:ph߳uaJO-.- =tP7Vj#'~ 7zA%Z\X-"f` SgNק_ӯ6*t~D!vɸ2G?h,-K6z'o5w߽vmy@ 8G$r l4tCLMUNѠͅ+sW/^KG+No߽pZ8u@Wu{wL=qV*aR2:M~睱ǙMSR .@ $ L|neNg̺B>:dL(iGs8Y !8Á1s)x>gLyw~nz}^7:,K셋`zjY{1kzյ0ZЁvb#ǏSb!/ta(8vn, K/eɓv'(EDd=25"r"\1W_ѧ~R.J)@" B7p.\ngs9TS!Pz9]&BvSn/{ݭF#NN+Vz+NMsϙl&o}?;q/Z*/bV+{1ؓ^ ?\5l 9G6ֱ"}S;amEڵv{&/b7ΝIrlH1moșLeeG]hDZâ#dT2E6JuSO>pFY* RP*QHdi}K$r)yykvਵ!2FF !.šdF} #$놌ltmuh9S ?%b?H} z?k sW:'|~; ;#ld2@?۹eN;DDN'_xޯ/sq^I,)ϗ2F͹:5)q.˛*+n7 tWuCst1iz=$\tӕɩ-qd{(Iɵ |Dj)$q2%1Dl.Bx!S=c)bR# oZ,I[; oc*waCU+gX)˫ EI){ZR,GgQWRDd[ͨ#!"cIለ'(Is~s uhoPJ%: |n̹nZZ¦%x$E !0Do?ճF<~jf|\X3h6j}4+D0@F8CMaɼK|ޱ;D7r>Bw2ߎB H)sw@ CB8뀀ku\ZX+yJi®^1ީd!{K5\'w6b;3G" ZGibfs0J*SG)r+`&M @b&zׯ_ 2g3٣G:Kvw>tHVhƸZ ZTRc`jwz IVhl&;555>>x6]Z^X8*\|sv;;kffgO<++ki\rX,sbVy>scG[آ[O!80y {{7_;iΡTΏOTّb%k 9:!T5E$&簶B$ dr'֖Nb;~s7Rҫ /\ k WKrX2 rul2RcJ6=wb|.k7l+$,ٮgè;8Ҷwp~+ߧ h7 ]~-e *Fsf‹אָ5"G0-qgi93OTəj%1Y1/3Y7ʅB)UUfme!#ߛ {IAYr~LMKu$$L^2жAHti6@JIo'xxetg,LRnُ?yub$B~gzdd?l}jCBv݋7 w9n2,ˣXqG1ɯXNL?y[60(s\(RNjuG`,FIlu61(BFLH"&FHAZ5"m[DdΒ9FQǙLF:mR;)hM@O!j'YZN 922QǍf.WbSjX\p%ԍE:+Ct'E gDFy[:k  kԲ6ȀY # ׮] aVЋ#'OWG'RK"Cp۰?17SO 7 {#@Cvp[[m]4k҂ "g#"{~c5~ĥ$`یWox/owUq{]!I@(BCgyKt+ Nc>=>61>ZOu2Ʀ('I1yd.! DtgA&q/^~&'8Y391eV{C)P.ƊŢuDa5MDvȑjd|j6W'xLJtz-ĉDdkl5''K\_Y!$?y2 s7ZA&0hgBZV::MU%oms3:M{׿}ia?LL>2sO<"X__LIlZq9I67:~,xfb|k峫s>y^ן'tG&8MU xo=·kɩ{&؅Ro|YbQ2iOޏ P![Y{#1GǏ3Ag-hygD< %W/_Unj$(UϜ9K Ƙ8=R2JxV!=r숯@<14Y ^&z:MFGGlp6I 0|B%9q@92@o6NӐu42]H'SD9@O}._[hG[ХBV[_sv¹v5HFfg~ݣ'+O%i:~9ީ@$< N'IӤT rbۿ~c3 l>6V JRw:|>YX]!_(1&nSJy&T () ւ#' qa$2R)<ȅ@Dmk[nf(ML&̮:#J b &\ȍfӯG˕2 {=|(dc3,䲸 vFREh #"GΥ F .pawIt774`j7u YEv-[$2"*8r콗^\v-W'c|$2ZɚjM?;eB#lF  ih  ~O2.\8owϽ|[Ril|T*5!e\.I}bL[g ek>$RяPM@')CqDukù } #Z܂"og>G_vJ[0s\6MS"+Lf|bdZͭ0*r6\+hXAӾre~8[=|m_{1bnnu =55Őqά՜L&TkL FF;B%"luGs '%>}g~_C`BՕN-wK٩019QW6V03;a-YBl] d ZT*c{Jz޻n+„6a.;q|zc܋R]o{B^q+flL!,/|?z3@ƕ Q\1Ƅ&qwxdWrx7GGǏ^ݖ D,꯿ޯ__hd2m()båbȑ'5_֜v4l *Y;hbZ2.B pQlQΫo/ Fswc+&N]L63LӔ 1\< ˿0%2.(O;mHFn n)V8Dk'њ!L8c2A?j0c#f&R.1Jέb#"drlX\Dq¹ <:rJ5IuhwΝSJ:tonn4],˧NP': MD%BB.791d|Fkk3S\vzjblt!¾jtlq,--MLLxlVW׃LP.W}ߋps$1P۝^KJisƆ۬Kcd/}s_xYݸ6[6Z K@Rd2$~; G#cѩ:kUG2R0߯Gi4ھBO͍+xLfGmL?H9{?П}!/s# ibelV*GgXQj# H$MS!\0 $)BˡjzoP!λBia?RJc6 j_|Ȃk7V^_#$gI h㏏T+i)fdCv}cݙ,>C :n.zm4+\zT J%{>7?9>NyR |tbH8Kś[KȐs!spB?=to=@I}}RM` ~!V;ai$qҸ|94'n5֟{M jҋ:f&Ƴޘv퉧%!GnGYv$w{Vn*Zu{1Cɘ jZ-x:3z_**^;Lt*jPd nsP^gP'5 y._Ij“9Bd,I1aRB>Ϲhw cSSSiv#=j#3 9ˤL4 r4fkX*y~~l,6$IQvh@*_bk3d/o`VYaYK-)|o=rji2y"=!4#N"k؎lz#j`΁@~w͕FV}3 W.JB$DGdw0h7ν!.ŇM88Ɔ?(dM@"O4%Fkm>zʷ,V+.;H$Y_|ɑͩLQ4NNd{]Z9Zɑ2v!w}ui|/_:7G)ϣD3!ڞόe7/_:_ 3T^Ct&k/e2Adr]&Z'|ߓYmҋvb!;=3笱nڵklbt } c#)F{שּׂ5 'xJ0TJI =Fǎ>(c; 4b8\(mn;Z+@Q*ܻv}A ˋ|?q :Q`,H IuhgRqDۮt+`"G6@6<L!HcmJz?tJ@0VL4gN/V[/wi觖5R[nZkKrrd,Dya=Ni޹CeRZrBiJBv4+S6q][[A6k[j\(yZebZ油`G6gr1pmmMp>:2Rf=K$~~P*&fods'O\u{zstttfC!Ġ8 p!FƧեvg#@ 2ս~zzk|pZG Y?8Wvh:mҨN?\.eCƄ4.tMR.xw=ѐ۞_o뽷}/Ƹ- ,߱! z~Ws-#'NsϑHma\֤=cccSSSZDl5n`\>7VWy~& 8tTtjrj'B?:~\Cӕb4J00@(R8B2FE}<4{p=9+%w{-rt3(b6w7 0je[~HJ!Ӄ!XSg> B3g:W1Ky.RJqlCŔ#! s(q# 8 PJ!3:ZtQCA~%]\[h\rJ07bkװLC8E:'^˓#/\7eɏ?1#yRn'Mj*rȴ5Z W^$I_}Փ'O '|1VVR!9`yJhJ4!q9 I 20q1Jr^|~Zߘ[LfY kuƟ. wnCJh5r)AW2P3tQ8 5rHDc~_gd2"4҇a:}C=ܡ=-(>ob9omȄ _ LΑzLZ h2)4AQusL00ƄQIA)M ![{9q)1Von:>p`ZrdPqv&MC̠5xޑBwal;Y$!mR54"MNk;o+vIҥ+k7E.{}l0g YcgOfNSO9 ȇ(p9H dYF6$&I"d!/k4ۃ~?2X6㧚 ؐ2圓\PYV7_gb6u~33~a tȢV7l4$v3${]ۈS q 1k]Xfm b(QЧo-h%;$e4(!L k)IajD o\ fH|(@82L\*P:EL0t{rvÀq@4`x@ g`5}T@EyքFb+BngͧT[~Ο;}?s_F~ 8bBiy#""ВuYhP}xqwEnvH޶ӭҝr`ݍ`{Oζ?"):G!ŰK\:cZȌHy\JK/Y_*#1,puR@Jgv0`<cֹj9gI5 3I|˯Q׏O,SBDC(v=R$UJ1MBW#0c~/Eps0F*Zid>]$rYXjd#d֑rt|A?_(4MO{Wy^XVk֘>Ip2AtN,/mcðj8WBȑ#RNQ(dj"nmֵZ5URr0tFGǐaRQJ!2NąB\)Yc8~MOϜ8vr!~l6\\Jl(TL2ruu'q7d2." 1ǤMc8c|^:#J`P4 ;0Y,(Oz7n,<āg\pimucb|뫫rS?Vc}}\3v/+7;;[潇9(Uc5v߾imJh0b &DW(rӆ݄ᝧ߄pkom7ezHۑ?q!s⵴Kt[jL%zzaxaJ{S zA&,zcie _{Fۑ'2pd,Ml w[k&30YgP}#C&R)_Jv۽b.Cg#c3gNpds"$Yy:_#" 6;0} Н7~B#~0 #2a8(|xYr#{zʕ- @X@ c쇱nv֗O~S;)dwSݮ~ xyZ|{dXBwcn͘XYG_$ڝtЏ7ַ&'&Krv|>{ť-c׋bTk=Ƙu\7IN WTBoV֕T\>I:bH)' WJ66F p(P:~xsU !xrhq%cБυȯ<3AmZ2b 8&cӤ:ujј_}\YYW~Q:b|hXIJ(.%=qkr`wJ]^[^~ݳg/_:}A_e8c!8.,rd\hMai .9ÝdPݷ/Pmw~;{qo~\x$۹BA9Otuy%B!ڼvrc}/j[R[Ε:@2aȈ]o?GĂ! 8r"rZkt)1qzx:wS0M9Gq|W>)bb8t1i~P9eSGw7~sq}.mF$v8ƀ zGFJ9ι88yD1&iujW*}FZujz >777p I*y[o}wу3 }38Cdl֛7RCf)% BDdKR #TƘ+D^o~~~|lR*j@twHM r.][kZ:33]fg;d-QM…B虩{>n7$:~fбJ~ A,/ί7nX:Xv$2ݳJh vZqVmwZ-9گG{{mĥjR+F&-Κ$ |@*S!6QJ_; x> C쎣=wkjϿY_G=&"@WsΞov}qIJGD1@B<~o33izl.oR0ƭӴ#ǒRtgG"Ly,9vq: z^U, g`&MS3Ļ vmc+!E F'ljCQ~"s B bwƞI@"0q?칫}s-! 0S9~ v/mw񶬪D)m4X&9g.ƃh+Cm|Ns9ڵ5DP`ngaaS 2ZR666n.Q7[})2 q03A1zw@08ciKZ2cw We\S>gXȐ1 bCiw/^ݥ#*Ή۩on[dQ)_ex<VJrj5fgfz+ |4G? }=ȹJ!$"Z 1fFH˹ +ֹ sn/מ$tĉv /?@4\D$3ś(}3l= wkM~ey"NE >85d˯>CJUʕ?W.?_)OL` f|O!u1ƙ '!)7o۰ Q 9iz %czLsJR)\/ i8]H84`m93ǏgiSfƧ'MM"G)uw=OF^4HJ^&n'X_[#m[zRZc"p (Jpྏ=pOR&0@.+ J8I]h*Xף8Y\^jڵzX*i::6699Q777x\x* rϴ[݅fGRX,sd3QrF|1I+kz15ccLL&;;;kV///W,ff#R)2z- Sc f, C2ٲ ryݭn1V˧NayIJ\?L\NZL^1YR\~h큏dfeu J(nJmX5 \[Ybm% ޗw{͵Nr/I3[a2ۥ eM@7i{Gp1L;#۟^77 aw enuw;E$U)U-kR(Ζv[9>T5>|CМXƄ+m7:ADc Y˜Tk0`llrIU6}rhXI Ŝ5Ƌch Ҕz8DjL@Z֢VqÉ1"\#GlJ#id8 NsӞ IDs@&:% Ivo6$%juඹ0΋ⳟ7=p}J)ůZb|Oo\MIΩ\=O^zeiiSǎ;l|7'':yҕphs;D@CJJے܋LLns3ydƟDK<՜_~{w\;nbjjbj26՚98Wn ΌlGAwrc;HnN{Tn[iuqi@Tl4ss~?rm`lZ]iu s#w@bm%! W-UT dI!=$`h\___V\~h,nrwcV'd$b 9SKW/Lδ:R {3SfT^1Bfi_x^:25zFP":8Y=83u 4f GD6\*V֒$B.-,+++ǎ\I4VֹaNE\kZXX.*++kz&~!ZVY. (H \l#nwx^ A݊ 춎Vs UTuֺ8J8c|>W*#g(^__ w|c9"v;\esgMCdtҺxnφz8_?lU*/~zjf|6]81B $88k /(fzȉ}L{Jz3?/]> E\v/\@`9ŗ4x+;19=&gFH!uoS>4v>#o)Vuo(cXw{GqjAQJk ֑݇w按gN:4 m\oMujw2*%:M84 _w@qF׮\VK3Sccu%X gv%$ &NLN3 R^`,l7}a 0t G@`Ӕ Ae.\qQ r[͙HЫe N:lrʤtj ۃ̑>jڝFLJ1܉K{f0&LoGvt 9?P2?I! z~V[}뷹RDrnD}c(d-s44 V#>;dZ ݩ3ݜztBVֹԤ3Pjaa[Fgj,0ڈax⒡]&ȔKYˈօ\^D٩pQ3.ey%_\lljFsrrzlthΛSđN?=_]YY^YѤ Җ#cK3*kq2ōM+|g>ŵ}dL4ȦСC.\eL.8K.^dllC\nzjX, a S.VW֢0KRB;xhzkkkЏ677ʥh&;tֹ( 0n+Z1M }5ƤqLDZ9Xgq8d݌ِ&f{ri(koh0.[>ph y>{㏎Zͱ0*|Vet$g0LNj\!8\6؉Z\K[?˓Ӿۍko-"7/3Q)--lu\Ujy/r(D mK$B 9GD .ݻ&=̊?+}Bñ_޽2#^aZ8&uAgj)(e~/Jd*us;͌OUkj3_\&PnUpLL g֚ s^` YqW*!vz\FNU'랧X!˂jL6v[[b&Sp$Zk%$ is]c@PƱv~չå:Yk\ mh0d1tD@jҰ?%*Jy qPđfI\GIWf:wϿ.Ad*t08d]xN?"{1@HЦB|3s޻mv=8-!etv]pdgt+뀈l[w@0 $)x)\Z|bok516:+/ګG7;j+'ͭƙӧ̫/++&-[c/]1\`%pHbnss9\[K6z^ZaudjDlq=:vʥ7b:439ovkk+? ./ngsl'?{_|F ]ʀi_0ŀ!@a6VZ\Z=}9˽ka~sz 4ֳ~=ܠʕk&M 2 #@q[?jIK~̗Jʄ`"6ѭv(T/-/z|0oC5~WIի  !4?7|?tz[[l.''GFFt{0+43@ ss\Iɓ$esa\z1599dabX'Uٞ1rZshm( ;NZa6eB aI$jni>̧HM\>{F0y?yΙC{̉.RVKfH~&Pl٬B39Ǚ(U!.e/؄wc3ˋ{Q&Iۜsmk˿A+Ȉ{8U'xĉãc#L8`.IIY䜳ky@g5b1KQ?:~@'"D^w^j¹ZAۥ)" M|<)xc{xؕJԗB۬=5Zܘce2Vyyz 1Dㄈ2LB8ffD4M^~C;vdssY(lmE˄%~P@FKQ0șrbo1-'-$m6rD 8Y@^Y_msSNxq/:JRk$lƏ4Ljz\Jm婑\4n^I![sδC{C'@wpk;\=ZL !stXjۙDwLvv{Cw4պ Owi7uVטaSr.u|D1 웜 |sn8|dVe3sWMOL{ƯOL#<s6_Pämb^?|/;q$ln~wlmm.<_v}ܛ#{ +ϿbN rӍC4YKpGOjiF@0ZQ ;7v^ΗcKg]{ xfff7_,kkO>OO77xԩS/y[Ǐw:smmm-<911~ҥ.ī덩ɍ.7K܂vv}9@8.6 (a@$l@vO1"@bА 8$!J`c>dm}?>;vVӁىGy7vRL3uC٬q#^$##R/kKs+]p= r4+ W BP (aIr0ɅSJv$pHN GOO9t4W(?YijUo )Q/z4Ik=gqD3 ˤijmb6rcsEd3NwsyER\ ZX(9WVV}r)+˙}zZZkn!_,+t:L&I8L&IRm 9i:Ԧi72 Q u!C!8cT+QC](;i8}=XlŜP'246 y6W\{s 77:WU8%e.Fq'JE|1m4V&O>!y t031?;Y?z|DGgb+|ϼ‹I zK[ rsx'\(K_:xh\?9Kǎ3b @=S():vݵրQx{7=kB|tM Rt uJzIvC{}ssK2.<ΤqQДwprrs*gX^3( [eDRb BnAU #H9\\YY-E".A,^֫'P!,8 L)9prnװfK9Eac+ Ce^ɏ? do uf. Vǜ;m$ aųNzstoK/|zfvgg& {FFy5O?3xzG_|$:pHPzn(KîA pv:pls,amR΍ўLK%"vm稘)6n`ɭpDB򹷯{/(TVW֖W/ °鼺^*ڝ5t4L\}Ázz{uU'0ލV<2S}+5Z 9ûowRXNNgRJɅFv:$jG?ܻxyR)EmA;;^?Ź+o kFN֫JG}ե(g_ӽ^O0F1&4bXkXoVJ8N?󂩯}O Vh6R /ccuƁqw|)Չ5HNPI1_vPS>'kroT}./}΅?22vĦiScVܷ:x؁j6q|r,H. ܶ!Nnɪ^'rH8Z\RG?޹6W6(v$Sʂ%6 i\:qSOkdt4MI !$ggf4ED$IIqK#8FʙǤpv睷$gJǦ'k٬s |pu&vjB`h )o6_z+gVW*8ow~a?ӧg?R s>ӏ=ə˰ Ę j INOMLO !N@z~% 8T&*٩щͩ>o1bqǃv b   "C@!&`0Mb$G~9C1@@pN]к=%^s*"Vk156 lZɅj.DtUGi^Fji_ bŗ(@@ }rj@|~kyc }я]tysku2`~anck}lrGD.ߛ~7WW7ffoϟ:ok7RS'=SO*_x>fD'}ϓRIb\ bx^70Utj>-hrD 6.Kg$ B%%5 )D3R(0cl&O8vJ0Z4v^ZZc(˝^j.\R\,˅R9kr|e-9eR˘p\+1 Jj666ݮu>ʜ0VWcR< |^+%յUklZsTf{mur֑QJ˗/MMNW*$ c0q}}|.xZ^Y. RI l4&1fkpDTJm];):EФYF!9H#1d) 6}?oaƥReu%BxM)16>/|>I=/sXf*z9/R/Y*tqѠ+*cvgN?3?Mͧ>+\paS#qlt̓Ҧ6SSRjZA&ZKy0 ]oTfFFZ)6[R[ ;m-FFrM1ݳmCR"AHXtq!CXd^9pXK\ JB9?%jt ⇭aX};FuwOe P 77A ++KNt>d3 { hP^P-z:7Ϲ٢l1 ТiB^zq7۽O?)<0$vh{@hx pwo7.%2}`]0"2q"SISKo^ y֯_9`(}ā1G?WS?g>TV9q:2z)Iz8ai'NMX)qQf9zdbr|.8O9"+dX6"2SuU8[g92Z?::a]\Xaju[~Η_\ZMvTKRIb &mPX~vq=?xir&wYMd e\qr"g$m rnKSK0G 4Nx28 ܁fg0;6aSwZ%<ϼkׯ]+?x{Oށ~g?(An.hT.c„;y )2QҕN'yA36R/sRn Rjtd_*BH]^] ^_Un{rvr^_&erIJf\.g"߳a*!&0&^sJ1"&ݥ6X 2 h@k~OrBul߁ ?FСc'(r؃N{Ou "2gGFf8R-&`G f3|6kN}<>58\uYc D:AKҝPQBk  M"Ƥj1wu=rǎժ%χz`]% ajCrce4{{w+a0n6\idW_>^s߁qlT 4:"iADB "MSSho}+_֫琵Wb c}a8zWT'xz`NA C/N?ƽCpy|/nyg"y!B `t۫ ##$'|l-._v{o¼,Aqs`ghO!A* P!~(X]kEH]qowj+j駟Jdeul9sϙ{l64M!c1RVREYDBqY(reo}Z[MzƦ`L///{Ʀq/ވ5Α`L>SRDmAGp3P &?CJJv!12z\k2m6#:7g \ = MT-Ht{MnRSR2W^yձB^2JtH%{lV8sr||6;j {Ü}D3XU X/y!w/p8R`LG 4:ݷof|bT*A& ,r-9c %.O4H ]Y=8n_D.r{2 % D P!Jr$ Fb|w;m0( Rxq?HD4$ $i4*rZkl F0G=%Y&p.9FKtmunظv~JAX߷of^X$aѠۭR RH!zZDFqZ Y΄nkRzrnlm5X..@R)j4naIq`̹4 9G:nz0.yzCgGI^ཟ{&|dwmWD5@E(;:2ghgg9=9+3ҎfD")$а6h]gUeό ?^dVVu5 蓝{wN7@G %-}˜KgZ|3z^f+76ӧεVpeƘnT]Z\z?444226 '?%0aijaZwU'n9g6Fk 3S{2D ~gKAgǾo^1_tWuk`%"r7[ _~1կ~o_T2'AhƐ1#rR98N864\E֠ cXh4:gO_9wvG llaq-!̮gy:╥ō\nǟX_\\* X[X_b}{nA9n)txnu|>͆it4%#"gl;tKS@ ,ƵŗZ03H+@ 5Q.G'ڃę"!zuVnw}N| c!]92dZcIsUɠ\/|]pWc $Iad{q3__\ ˆ=xd2aԋ]nIaY|{ywyw讒`gYkmq c)y(N_9Ͽ˯}LkEN,8RTҩ4,@81:ZN<)?޻9]q=CZ3<6l(3(+L6Q+ a.dikfrR'uFp.$I8IJΥ Es9kSn9>ok'Q׮nd*i6RR!7:l Qz=Sc$ln!^q=['4ZG,щY9gtOku q`H8 "Lg!6B@ gƌ\^m?x 2xɧҵqM dwG>ڨڵM67W&fQؓ:jxz!7:+rcI6We|x}G989=Z* XY.6q7qA ALXG)Vߟl?9$H:}Z \rF.#pN B 6Pe2tkؑ=pB0Z&Cd !Rj^z RofL{dqW nVJ~P[^_uZy3ae2\*T76^&hZjIbNB>#cu;NR.[2avb|粹FŁBnAJ%=Ovpp |O9 GFL6cf2GneeE09\B$ !"mmKh˧˘N$c\[V hϝ1fGq6Zsni&G] d=!q}fl vN'҉m[_7kJfUk_=wsz/hm֟s?w衳Nۍc8y|&mwbD #L/J@9kw39%%O}O̓_X\XcmP*+Ib̕ pt$$ k09Bd(dG6ID)rre188\]//V뭌zC=P =e/])[)!Gt #gGrH>g 7ڕkuz!Y#<սV&˹Ko^:WLDB;'Bkc:w%3ng8:,}pL=->B4;;~j;1N6Fц1@䪫܋93ZgcVlNuZ8F`drC9{Ǟz_}假OWJkaιy&'{ry1Z20zN&u|mXG\>W+^ܙ7֯ط𡃞TgOi,KёNw£t^[[[3;+q̮\.{\\ TY76:fX_[Y.}?rqt\j6ۍf\zܹӿ#{mr9si,lL T6kde<ܨ'T{Wnk+ͨ斯]]s}N9zq\YY&t9#Hg%1Pc0MlN#mZV'BHFZK`$m|#OW?{tyᅯ?o:|p.9?l6=VX~'G=ZNS\=/Tkř0ӍE{}{rHێ{Wq댶:+RI[fVo~@.v&NMM6[z^X=t 8Pe}}ʕˣ#cSSScc#f}}}MJf< 8^$,岾6PIyc.xӒ0RGQL8l82Z*mT:ћzG#ã5u['F*/NK1;Hsi4AbØTJBc;s Rjd'ݾC5|G>z1N3Xjw:{s`JZ9pjGW7*|ā n%IxUP 38BdZ)ҟthOX~ͽ׈q8YXK/Zo6q.ǎ:rpt6ՠPEa #耬rlz`lr8n.2omorCmKVu;L2Xz(nU8jg-ɇ|;ZuszvV0dLik# $8;;LwhX[[][x;gu_r *,%>#4a#0%&ȉpyQ˅N0Ʈ\g'z܂Y\]ڻo<<{wݸ|0|>?ƜuAl=E"^_K<ϳcsׯ5VY5=M%_,9ǫ{ >uzcmrF+<~Hӝzλ; 39Q 3(WK`<>\2.v5|/[>x¿uƍkxͳϓ$oBPz'N *NڟwUaB[N.$+OπxfB8Apst=\·xL&ۨnRnOO=3}gcV3b";K hllVz.Vnkrr2_MyR:gqd}b9"dF7|~vvv4a`LyTe3N#f$0[8GQ*8M,el_DXVfv͌ @ٺuV&ٵk<%u{ׯW*ٌGYYE䖖21"kIfO8Du9 N0ADpNwc pNYt:-3Y5ZK|Kw:pjDdNyw|escV9r^yZzS?t/|)7;o;CbSgEC!XK,Žg? hv>(VkM_{oT(dBPP28AdOgP\%U. c0R$ԙK_Q "/{dn =zRuڭg?~kW{Q4:6:kW^Z33\nrbbNFۏ qmm@Y nu׎]xdžЧsxR8wzzB) `[k)ԯQ0ER,S!\^zsjQWFG* p= os.vP 9,Mg dSП@Hii^Ȁ^˵ݶqR!Ύ=<\\U> qݮѦie OuݽkNgL߻xS'>b@pE?u;NruehPY? ̞=BsƐYgɥk{8G?zz 52DBt yΞ?|xч};ET'/sF=E`:px( F]d >I/YX?_}kA1_!f~wTƚ$IwMy0ٳoI5|ߕF2#2\6_r)[F0+V7MOM9Ѫ5z80˱@=4PWxLVC>yb=6*dfWm47"ˣA TAmv+婽Ξ;gdhgvt}].\w=}عs磲, Fcz"o_~VO#WJsv ;Z,lC0_W9Ç:^,&v P*gePƃ3^xpzxLyP9Y" =s^εk7O<s11cYDg?gfm}G~ Y_%cxIC) xn+3#g|&}̒2qZ0sm`fbj}F}lp$c!sN'Ii<ıV\AڐSk7ca(268HXזn8ܻo&cMƄRd?<\VC.#%cP3d:JIb)M@ cJZh*KD`u:g9im n̄9:vۋ&5Z̗W_[SgZN ѷc$o޼䓏WW܍g~=Ÿ凈cY@DBÛ5뉭br&[O|칏rc#^ b)9G EO.3Nŵ__%8"DK "!:ˈ, Scc>LmWv~7kQԻ5ǎY^Zr>ӛҒK(pSX_:W'kC_=cѡMR\2:\!nbHY{<}R2c $vQΝx.~Ld=c"JZjay" 6d@7'kYsELtG/-:{!%Vk{B.|O 繡mE@p0 {t[y_l Px3dGo:7O]WΡ!)UT9Z& \3of;69{^<21;Dnۿ#R|*%¾&,m%|f˄nn}NXE.R?Vq@Řp+|>#;ym+ :sAS"X3G` 'ɳ3ۿ?X녅j&đDVۼsR.;~lϞrȰfpxkXk2lӡ? 9+TbislbѱbqliQt\ biB;0 r@p2**ՠ\~ 5zO>"‚yjz`cϝdLLklL<7;ٳwXcA1ƇG0nQ'I'J\^WՍ5A{Jt4P)Q}?=*<ϷDQF/zs;pcDhŐ=>(/Y#"Pk eL3(cO0:j/辑}^F oJq&q/}Օv+j4KK랥N}yLyxV;t]'}B136Q) p3gSNr@I3n"Ł\exC:@(%ubSd͹OqƸ1F$E?Q;m둭  RdΜ=/}CQ K.58Osssou@YdeeSS0RZYسgVJj7ox> G-A2:TXYjG'oJS{xnn.9\:N izB`2 LS׿5X3} 'F` \lm!cX$R U:iv #mAdzB,_vw8z?#c!zuFˋ/]44<<55*X2 8:,:rN .q }T?͹)Q릿 Y"KA`HH$f+,W7ky]_y[Nϐv$pNF\.cLRzݼ6w MMpVtr!m\d$;$h{^}1J)GdVJkҠGHѲtinqnCX6Yr H_9'F[g pJu/(Zڭ`v˟;XRVip I 6Q;RCG:p`ߞ!UKJٓes.V寃C# oB!ɉ}RDdfwA9cbu\~jj*"XLfeZko?ۛƖa~2DdAp O] l`"W3\"CBN@I6)` [!V@C @ӀO33vS~&Ym/"{I(}53+d ZѷsRH!S|HŠ\~LB C%:I<@XD's4 yl)os8N^}/(QLRD\ۈcs$4cF'&8X359Yu>219kEQud8G<`-+ݞ5y_._d^:C'@Q穧g7-By6o-T TYdK8Dvm"lZ*8gZcw\X*U('/k.37Yn[,;L0@m10,w6C9D$a7hdf8!628%HLy=}\ƳOUa \}Iơne2.q)wR5mqΛèn;d>w%^cj׍l]h8xzʔ 6Rv&2a"c2[v !Jzmt]y%x'q/ <5:48gRHhu,S,1xZ^^eLnlT恃G{{3@bKHR Fu͚R\.9cppp0IՕ8gggRzm ǝJ);ݎR*F5<<A)I;/I3=󥐹\9 dd 83uIqsf[lHwnYO]o#qLg޻;v7g>?{zbrxpuʑ:֐ 2DXp ٲ׉ǎwVdPyka_o1NvCua֜#_L._;kf@A@qrdDG4D&C߸\?6/rlH~jcpU.1W~wޙ>s9LqoտGх ^ַ| Xp ˙0l6|~Ѩ7zhc}uϞB>S(|k6gNe2A9~WNrHuoQf,d2dn;ۅ2i Lx1af|Zk+SYTj UU֝%s0G`48C!Y#dH,'3Zh֔AW\p] N''>ɨ-*D2=TD sqOOBE'N/z*?z򛕑ѥjli ID.qƐ,sݸ3%?-7# I^ա4D78p]|G[\\af2@a`6Ғs'E4gH.nM98+=e\8甒BHm'"񜊺+C59uZ_}KW.`Gׯ\f-&CBH!@r@'(#f]B ˁU'>6{W.Fv.!o4ׅD@gs`9c '<'"T7;{_ KsWΞ*%L.>C6;\yG'@m4Ҷ;Mam%B.I+JB f|?P @!r!CvD.%';6MPmB ,!@kxc5u:(A<_eC*f@) ׫H(j+O ` $$Pc"X ` `!` y(J4aAj͍WתGÕ^Ԯ7=rG?Sz ƐKJ,pa;q2$Gs[SKpFI.Wc@i9Θֱ F889C #ܓqlͥZ6111[,JZ fvNWmi;fsmm}bb| DT 0_6U嫱Q`X@1f3xZeW*a&3::$ywmzBJ.8<%UtQ137SSnwVW׊b>l6{Tjhh(uh!E6Kmy;O m'Bn\sz1V,e&;>Sd5ddϸb}& -t(O`#w8w:Pd2[o\ҕsW}'}*J*P&P:vR1<jg}۵{cS_3>f]0{dM$qH1<_djOG.־7#͛N_1J^[HqFȱ<`yy֭[SCCä]4@0GS D$IΆ>:9@DL/6QohE=k`jNo>c;ouҥ|&<`Lt!jkB",!L0u8X,!g7Bz1!$W8B1QpƂ@kPtB  3 G9`s{:sݸKJf˽љFmJg?3C#wKs}NQEQ"KF1J햩;ݦ" wnxԸ ʨkΜ_m@ʃ3Z)՜#gw),8(J΍ayKkuT*ll8X3`vvv`kdLaNL`DXc <XER4=5 R&nKDʀ<ݔ_*9ziI!(~I2TJjm pimsQc Ô4T\S>A$Jz!P[ Yk0ѵ۵$N2a4=725:9 GΟ|[/\ijkOs/J*U'8/fLe8"?Ac$`Ry|\Yw(q" >: GY OS=3zÇ_@#0??1111ZzuV|k?4I d6ka@;ۺҀ!"0]p3K}nmm{Z7Odƺ N͂s0qQH0p'g#oF:`_~'KƸ5e +AⅥFoqӟځC'922rJ#>؍c1w)@ 9 lt]π &?ԉ+WN~_ʷ_ʕjߵ: O4z 3 ixcc)RvՋQLOm6n޼yzheeƍA6{ŋǟyD'rhrSc=e[~dii3@nT,*]L,\[[Ds>EhR޴  V2=`8\,m~ 5꛹\fztPz)?tA(2Ik ,I4Lu IS >|gǐy?7i^z \C񇋋\b~С׮]dC[6av[k˻wDJ9%q$8w8|!lXEdS)κ$IYަ@:_669 PD`1A֪4,dr !ҭ]_JB>b8M @D P#"ej%0٭6l_~ߜ}GnWs ȓrjlT,t;f>n[X!%hL_X$m1V{sNN]V9_;6$ƙs'#vK7N:¥vK磴d3ݻo:Ibv'rIAP(ւseQ򅅥8֭V6|~hTX rZqd In[(}gR %GFJ(JsyjfB:}kwڕJEa+._SO\ric}T.W;N.˄*֖+ srnn^26z<ۿh @8ߊ!H〄 e9NB?dX(`c I2qOK{} J΀@"Jv$A=͘ 8ш .^IF'uGެ\>ûXGC 1 DS?apX.gzH%g7o-ePr}p1,YMeH} mp"Y%'Wg({; Gv{hi6B+gJ]vM)5[DtywM_zܹ[| (ڼyȵa,Z1=k WR s޲)} 9C \f\>쟝O=[]Y?;kt! a`F!8ڶpI]o?tx ]9'6-,-\Η_~mm}idtHphZca1 9-,cK9/ P`@#0))T 7}`2`]S +ޭ({u[~ygW#XpC.BGG}F;tsnYv}or׀ť_DVz!w&qNYϘPN*zaVtJ\f.3Ểh%GNHZ j㭷\p9N`>" V*;:{بn)%s\JAX%C+JRެn]mn(%djɩl6ǘ^~J9 3a"zvJrnTW{( C!)EF zJ-zBrJAy i-H[]sAժ'(Dt:I!ctzSODX*eim2\epee댉(6W * <ca `&봇 "c5ε{|b~ÿ́'8w;<;Cmsƽ\ۨMM\;ʻ8K9:΍+WjkO?h)9rxpݭ3&'FJ DdN,hcO/<}4wˇ?RVWwBpl d 2 H5;w>s}QhW,i{W|}!tI.%Y0BH`$ 1د}닷*~o~;~}~nÓD+~ǗnN KhRovZԷ^9lkhðs筡A.[7K7|BюǸ0x8!F+CCdXsC$&+a)sfy$Y[ٷo(wM6q6DQWky~ DŽn;IFFMݝN;P*rĥPQ9 qq|6[kX . DDcMlLFJ9#"mT9l6{nPf3\pk 9SJcit@J)(Ki`6isD sGDFfLF yiM1͵$]|wNs&'rY?ώM W:9(s}.;~n];h->$2GDd1&8F{F`w%٩ 8 ,+W|7µ9ފj]`8`p"^ݿvٕ0?'|\> m8c{:Џ0*8P,jU1?P7>>:5yzs™3;z.90`'Qs`k-,W]koxw%DkKدp[ C?W~/8~󭷾+GXalw:NFu"Ʉ 'O~9˘NURzuuz|xzrnVxkRvhdБٜ"0_Sciˊ \[!gy%c7 zeOdR+?-^@:@܁@ex7s⵫7V2a~ty~s)RԟDA$=&%8n' cGw3O?|ӧ/^>{ZF 0Z):[n@žitZ m Z- .H$rёH@Fq$PȥHX%p:ldAj!rd'y聓B}IE+ sʑY.C#`ȩ}#Fp r߶x!Du&4gGMFK2IOs箞=sql|;~Db!׿4 knݓ>h5.qhfcޫ7{^Npxlq !UC0 ƙGt?oT_ v{Ԭqno\ _}ˋ=`y|,O.i7ruD@Hi.RS c=v@qohG m֓w9c~Qx&cq&>arf2Aմd$RJykRbIV׬O|!76:20X L𛭦ֺaq# $g@RT:ryD6;ojn{`E*g|hx9RJNM .IbVWWyG)#g+=@RJGNp@Ib۷4cPN+dG"KK)PkȄT}F )燍z+Nh% PY R1O1;BmEr`" bTBpm='rO}䙏'A@!NdRꨓ}g>Gm0}™K{|? u&׎|.SܸEm.tPCL"2 9g@$Z) :H4"ݶim&RHU +9SRK4jZ[7r-c>Ο\?jMs$Jy^ϯ6寿s ?61q\ɀ#=ps^ _[]=%0a]n/  lln3o^%8gĽschݨ׎9بolV˅MtKY>3Y\qZYVFFʹ5"}*d4!1d@A/OPZ3zF5u>_50; $D i|~qgBJ`5\{~ @0D.7!!#Lqm{Z-8:J! h/;b. :5qC6A )qvwwVI`ԏNߖ>Cϐ'%J1@tW К])ߚި>TΝ.]z}c $@VpDG \v{]r$FNj sDьqӄgɥ#B(ĝ:&  5 $%C9Xg) SR &%(1O"c̡p,Cb;0d:D@$^O `EnI^)/ B@n8_?{⭅[NnQڑ9Bp 2٪'\!N nݚբ(N.]$zݻFrLt^Y|@Kv|iY>(W2/RqkP{L$:rNw;l6y>"pA tlht*Mls.Uj֋z2Yk5'Z* @iT:FJV VHK0/"\$$t:tb}ؒs2ًkZ/8d-P;CrF`xKO|cZ}޽#S׮^{ݻg&nV!sy|U,K3gJ[qԉ-)UaԱ7-M&L6 8 74o/ fv 5㊑3R> Xn-!rfe2}"Guk~e}}m`899:2R*^DF^[*}­?|k'{pY \JOPн3V|'#gj ,G #@)$1b.[n6MNO8x}ѱ#] ,G=(:*C߃_-M4j9:N+.l>HV=0519u7nyju8 d F@N"Es )hpgYGH aဌq CI osÙArBp"H cYkƆG6813!o{T(X[]t&|P0}]c%B@ :KY@jf/ LN0ƞأǏ:yN'rBŜIo+8ٟ6+:j|.Nv&Niz=R/Dpv g\# \p#ᤚn>ӿ[W[I~?rR%~gLcc#֙N6[ [H'x=w 㟷uv<2A _1;W^zӉlS3`96I~jzk{V )766Z8ALP)ș6(Ibp. HIj7 DDRႥ*[$I¬(~Ɇ'=tn[,"2t:f#\.Ap^( !\qM$g؈Ҿ7n-nù`\0⎘ֆjF_;qXG|g@qF~ p#8$1NwN⌳^GǑvT!l{ ‘]38!'OC.n#iWb+Y/'FRXۛs{6Յ|%›K+ƫ?q|h#Ɛ Ƙs11 `-cȴ1Z,퇖?~7,&p .~ye "dxūkF#r1>{6ju&yrqVZ-25nyfz˷' [ks349DF梸-0\s+Fⅹ|.31/dx6%\bvq~>e-N>Ѧ{~&os18J[Rͭʥe'@Ƚ[k/_ywGFoԛ#hEp&J8k3vN< :ȣn/QzWfw}c&FWVvmT, ||ݻL~hڂQI 'Q2dA4YK`) S+r\@I/Q RȍT*EG6IbSI:"|}=FHl;?yGsǜ:| )yKzQ'_FKp P ,c9n= x(2#3ӟ䌬MEZY2i47o:gMLK\63~RȬщFGtVWL={fJEDNJRxqͺ X19(O [8aV R*H)11L];1ρKSc)cG Aϊl @!&$1I_tZurŠX.WJe c8g>1Թ- ޹xR h6# J{ ;I_>keKf[Mnp:$:d"q_[ /z[+c#G.봌qC%H{ݒ:gNxک?xA nTk/|v.=Q0 >(~<8ڬV^Wb`p,-/0àMNNJťՕUi|b WVt942ՆcaBѭY+qԖFheyeiyax6R/tADZs Ld.ε[qڌ#;;;\|GG4qbeAD@pDK-dʉuЧ+!0bm@igWVjK7ҩ.NMʹ;-O|\;w @^$6UP;%!\rkV믽n/vcSϟ_^˹ŬD%X1,70&F~kgQ!=SEV՚_ʕ!^t4PJ>~qyyA&|/TRE("r#!R_xFˁC'NNP.Q%\>@^dcGw^Z^\W ,9g6w\;s#?091fLz}1 ">>*piID#2c=|l3+DĢȼQ[o]_^ wZNN2aVHC̴G[ ȚnZ}SL՗$87 @p8x`qhϏ ]|>.d6ZnyFfRE=G6AߓsD{ר4m6vO.1w_|3`PH܁@΅N8ǜa[ wX&1tB04\IOHDI՝h Nؑ|NH{RLtDdd30xF,-\rYFV( o\>O@ip YȐ #!ENucd{ܳ:kSU&I-\2z]TEg`+kk{SU\控΍V[hYr Y^{GP6ཨuH7-Q ,@q%ݴ8"O 挱܍"L|ᕬػkG@IkE:~8Ӿh ߐ!DGATsI$e$Jz*kjy%Z>B0ܑwnVРY:1w\wW]?~L H۟rfsbfiűbޝz;q8CzBC/gvZu=FbGfkL]6Jhisea-5}`l%֗o--߻=<28{"[(8g9B~+@J ѩwjׯ/\rcxxP(prytvҖyʟݳoj׌ȆN'Dž@w;H\0\2g]dPCK怄(c zZ\#Ν'?p`_zT%qȦX.kmck%esB @k;*o=Zpj޶D_W+]ԋC/J]n<^C 04 9r9m.NFOk;GhDHe~r'rʯo˗ꍶ'€?vcǣyvll<ݩ RΦtteyl^hkvZsv啅Ņ%44g8ΆaR(!uȔ`؋:Cv;Z KVw}mEypI뮧[&9$q%<&%Nj{ݘ1熆+\}ΘyF%x(*OEqoueuf Ct[rI4^;44~*ݶ״u=9" /S">X΍I|߿pkShǹp0{'jsׯMrrj0N:rD|/ѝ0α9tƂIP((Al7"$i8i ;;:A-@2a1.;yX8ƔpM!q&Q:0Gs=Ϸd) %LzKk/r:y=6t?}ma08 (N{>gD?ӣw} 0mR^Kyd/]޼Ӊ(Grd8(?Jbypp17_Rw@@+ىhaai>|dxtlV[[[󔧍n4@]rrarL&qHevr/HTuZ[9 /2u$G\)FN͝n/YYP\{S**SE Q_eBpJrmL!C)%#?F=]Gl i:rFϔa8*^ҿtzhcwL.F Ȍ5-.$fl'+sǝ5ӆw޿F0F!9D7n.vZ7 }>0{c] fu,%"u0O3ۭEhh,Ɛ*g_r)Sc M3_$@O>NW7kR=t8t 9 Zr?A$g $M gWW%dq$4%w:&%rf\,:1T Lt]SjXf1m·4U!vP<.{R 6a!8҈Sֶو޼6kks7nXcpDt+HR~xСgxug%@8 fjLr@r1;R T.!9`[n5ͨW770hJơv])rN GN)$1"7 s++/[˫+g_4(Xm,ԫDGlڧ{"u:Vob_N˗/޸yX, Byuݛ7O<9:6MlռM,٬jԒ32;k55Ź#4+g]_t}ǏNM[#a, g,~MpIVWP#={9dۚ2@@ dmxOW@;H=¤ \b*FڮW{3IVVOś# ZK 92֎.Y|7 ȘӚ ̯WW7K79Y,=̣H p}$%d4-Ru A{)0)22=;JӔX"J1*HIbsHY ғQmtb"`븧6v9 #k|FHO:g R<xO? kf#pHu)"vw n[J?8{2ڝv6Vt;ږٕB[aR?z_/:sތוwZ8LG?;H&n`h<.k9d U|[/?9yssW"BR1<<@9;118DQHK ku َpގ*JSP0IH+#wՅ[ڱ/~[ +k"` s3G`c}fT,I#%u%A)RFE$qJ\sXU6[}ntb#JB,zScc#DT.뛌\0j(J1f.1){Є~rOs39cN8Zk_{#`Do5`+J<̫z߿uclԛ cm X( 7Ayjdt4VA6k1vMOWy9v*3@a@LZXykin|b ŀI4[ /"mPi <&8?{,陝 bsz_UnyL6ЍvC6ds4̮Qh#v$bF0vBUh5+#EljR3$l * M[@9QT͛+OqUۨ s궟d@nQ\a\v[޾'nϽ oMF"+\ɜVlNGQ!aOLLy8NOWW6uwZ΁ V9 zknvd!g~o_OwWG^UWðͺ I8Jf$I\5cZ]=P'I=+ۓ%dC7{;&w})ee@.jUˋZ5usBJqđ.nrk84N ^akS+X\Z5bm](Rӆ-nB{g ]pquGd3дɋ?~3Y!zU8[::O/7;?+ښ)2l|dY{erDBs-j(ZŅٔuρu -ڏAt {WP2!s$˿K/P )#ZMncw/)}ovјeauKɩ=#{FF::ֈh=tc*w߽UçXyCGkVg&n72]*VWVV֦vJ.weLp"4C` V+Nfs9cc1~{捉bнucG{;@*G$2}x>FBwcC߻.=d@?ЋRR8&IBfLaff+Z]E=s{f]!,Q0!Rnw7%䶥tyv/C{m[ - 4dz9d`-0jM@{?~;7 َrq}?9DgW~t̘:X!E]qviK|K_mf*p1EhtbZFRq P!q)F):pƹ`ZlF$%FG-+ Y!צk+{;?|aʙ7.+ Di IB5U_[gle o7=}D6}OY($$DEȸ{M(aEls'Ms Ч~׷jqTb9sp.lɊΎ\~n4|?Y /$u7 u65}cdOϿfJwp",#3qd9o7ַc1b1g6tbe.zzKo?ѩ-/U~? I&ܾN?xAގ.έqƘTHDAm(Vb܈<Km?4l)c9 1|zr!l|sie75]j;2 bm-]MKĸ=)$g 90зh$CnN6bin.^xjj}D°˕K9m!gL i0F!ךӓz`Qʸ]ו\f=#6{fkfިer{LVH\*֚Z1؀`k{ H:;{Rt,PJicƀx [^_ H+=<<44ؗ/d:GF*`mLvi6oJnm_Dc JW2AJc8i%)7pDrOΦ~F?}Urod`4Z] ę ]⻭Vj/m湷ʥҡμZ>߿?&Sh`@μw@2K6c.8DFhkayZxбcGVFXt;ǂMOx780xaYUxrO9Uvj/ƞ'F3n;/M-̖w^"K @!Z&З1Jic8Ib[XYve|m65598::JݣcC=E/pmRo0\pĘ@`6iuq2h}a{]BLCܯ~f\p əWN-.WVn8^HzzʾގcGL`d`-ͱiynNvv}t7sǛ3S$M#Wj.^XYk.,Tw|!NfeuGO'RR5t6U)DI$!3pS8s [,n-W+y ^zeru?l'J O 1R!υ8]tYn 0l#1f8S[_Nޞs:9>7/1Ih$w{=ƌzBOks)m9(%Bn:E㜡O~zY`B3N,'-l% @j!X[b^w@dܯ|KC1, &:V:!RQV;:ʥ~)L5V3[͐9.Y&\_ |ԅ W]f.уBVI3`q.R!MFCJm6h]@Gg'CDdƚFY*%k1yA~Fkؑery p?pQqfr%VG۾ۮ؀iғ%rμAbSN s'lɶK߹p1W+ VayjΙs_kfW^}ٿ\w' C^jwDҲLԫuhmmkX; ֺYokqĔm1ٵ?L1.0:mr%`,Od˹?ǿw{p@K_~奥ᑡ'zw/}Nx~a#ɵrWЃG= PF9't潞rkW'9`iM>)XS[/`fUL"^Jp/߬Mze8 _r|ת(BB"vI]0WƓwH|2sEާOۉ^ W.kg9 @ Epc|I+/<^'jP)ΙщHex{i`5a 3C62&sMl]T+ _#=L_yjeR[j%o'Ar9 ksS'b9"hPZlæO-Q}#93ڒȱ Ů|xJJ06jXϾ0We{N4&[{DhÁ 7=v.ڌfCHzKm<w~~ٌzzeYYven-R\-N<9<2cd@J WeȌik2"2P:n]\jGfH ' l($ׯߺ,-W^xzKd"˹J £d3/8^cw28\QS rGJ'dJܑ#f3j&Yq;; `dPzR8N f O*hk,5GHI-4gjCfS4I6cު7'[`HoL+8|CVYmT'dޫFD]Y^X;=XEBWȃB n.xX][J88S ƭGǎx׮_9zķVo1^sټIdEɱ})YM >_ڪ2Ɲtc# t2J!0!Xۭm}# ]p`ȾrD{]xyeeyxdhvv1#(%r.KRID/奵^M3=5QVOW,۵C#}==G߿IDem;69m<ȑn[$kss 'f}cqa;2Yr-/U9G~t```bVys=xhm! zڱG"J)fͶ{Mm; H?mHsŅ\X5Ox+s 5kps@%b]%}{ʅb>[xzK4^&3At<:=5k-sl&l6NrVs\./.-D>+wEDyRJcsr.M$1bsG:Ddі;GУ"C[uɅ.g[o^'޳w;jyKdJ%q[н(-uaߝ/2;ڻlh 9d Ͽ\m̙W $QHDZIh/c=Ǐ%CcG ܌0O … O=;l5:˯^rcǎ>'jjz:~Օ۵U*{&Y.fF~0{T#RVYfGGmHiw%q]d^uoO ݷop7w Q@-+\ heKp)3\x.9 W-c 1ƕ6ZC jߚ 2L<ȃQ6ő~@1@R0ZrANOt>՚؆P+!\pc$HRr$v%S vod VfA{P_{ݾ1fcB s2@q@ TZ+ugi⤧~3|. #@ALp[k pY 2S5Ff,3:*'asx3͉KWn I+S7-SK}=Jk]3xd\,R2͈[ Dz?~_ү"/}٠bPH94B0*vwua7zøaLg&@j6 he hc[t{s.ces.&D.z.WR&'߾r\5ƂZȄdsf#Z\t LG78̑ݝ9Dwd~7Ag~3ɬ֚.wEhQ=ivw|&zWW "X8sP820l6759 ry0Oި(5e~шsfiTr|GWG:i˾Ҋ1WdHRx;nЎFh {FQ"TZI)9 Vj./,.-,,vtry_wtY`㒥(nv.N|z2R@hc2($C64w|$I$!";#azqK$VGz&xBnѭ8zyjF|RGG뭚bHq8[fFF^]VWWZ幙")zȥﭭMb]cul+U7(j^{cm1scSG)G:D PZ_.p??O1pIqi #rcH⥥r c:"!+̀<ؑÃ{W׌Ç$tvOLOf`ttt=G-//;\.^T{G8P`j2(.6B'}ܥn`&`nue?|rvzQ3=]ӧv m ¶.'mfE&"`N[q4m{G!mٶ@:VcHd2C$#x'8-S0% P }6դ%x٬7\("|H݅Xlv,Q1R۴ n,:/]˯Q.wťUlve,7;}=]w|.{H])򮅃`!??=s*|?dcL''HpRjumllg?[gL6[ҹBADR(~6wTTGI#P ,/Ǒ^X\y¥ ^);jpvn!T:2kq-㺂jl(@8.3$ c{:ۿ`jp'{NdzZrjmz~yg97I r1 cr.8D)MQuR./9rxee#?"hcmjf6F . c"[cZh)7o$`>6vùx-KX(:; r~pq 1`{#w?܅s^e%Bku&o~[ko#Lp,H! 9Ν0LxW Z]N6fp&hi SUl%G@h;=5;@.-[zR)fo_'PmITgO&qP6q]r7#2 1ULۀ(e'omZb]h D9[sV=nsŇOp\2 [2J[c#aé(ub$Z{~9c !Tm [yI3j4[/ϲ7^{M Oւ(d l_HenD b/~qw~a U+2-bq7}{ {R4)"+#:p`d3/~zsqiϿT,w_r^4EO 3# Oo67n.H䱣GF - oFWgZXk2ٌ<#e1Fƶ5$N>Zq9 `vfQ(/KkffAKbzo3/gf/8Q:P8]!b+Тiː]Z_oMl&uĵY dmc+6kn5-!V,utGJMTRk+dvjLXa+Le|a r|+l*@?z+6~h*o8ٙzZ-z(}2+i8D6Lx6g)4S8sָ7nL.6s8JJJ=FxuוRj";^Z.^aG=pߕ$jbpެVt-声eY~(ii=RZtD/CCv0mcDohi`` T Ecq}gG/s^Rvρ={k`^xÇco}kC;:ff&>uog3ٳgWz{zJÜѕ{ýQTȎOMό?ԉbg @9w-;f\ٳW_988lV }* \.;͸ h+BOpan XĮH6 UQ3ȑ٪D?u#tgo0j^ /\%uu`Ib8 q|RSX`vu[ہJd%c=N3l:z?D+0)[eʼn \ 0>R.VWTkkK!s!iP@d.TI e^]\ɿ`}ꉇ|`dhpi^ c[d]- S`S>~:Mh *,. FFzNt_OypSN//Zq493F2F),Y`tVWGWGGyqaqS''j#NN[Aè( 3#΅F%U;wwYtǎ|еWc3󋋫ְ|ŅΝյ,`,0V R$q Z#$8 {upuu~p9T` N 46 ]?[2JF|>_T)c6I)E.b]3B8 Tk\.9Թ1nu]xG SO_@H&Qly}0?ޑ>/@d2J\ś73A'׶gD&ˣ(" L:vkj'6˒,ݭڼ鞶ݒMwT . wTpܓ=֊ r|ŗ[)˓M{+Nj\p: }?_03˗lΑqIHw]'Twre}drIx?Cpnjmol?6~i3w=0Eeu"˿Z_WyUplѬl5bqlh\jkWѧ]eH?Pk{O:]^Z8yCz HWk&r.9)`~nQ_>y|={:VW7Hf*a.-/0lhtzhtJ' ven%ژr4666>~^= Q8.LI@8ADhƤZۍzuC9CDڒXc%Y*k1+W w޾jiWqy=eBEw8SANWTμ׿FE>ȹ%D C L6{\בR={_SO?wϞNͭUVTTWz{˧zPp\\\*Dksӏ?|^ˁ' TVW]:iuY 8ߛO˭Vk$>t=:@ZK@fY|Hdɨvs3w򹙵Fm}zjW,,>ǎ16.‰\Wn334at;i'L5QmE:Ż{ƒ>   6݋Dp>@L"=خKFzxza5M z >Q,@:.%@ڈ3 K#~;#~Jwfűen}xX%ښL>742|3C պ,w޺| Lk}p}1ɹI2Ab^٨RiˣdTD`9h-!%==53=W;9?gN=pԩcʪu2g`mk6L=Lx3 XpY,{]Y"( g\rXkmrn&Ib}ޮFẼTsReAJ8Jt }M]Z2D0~1tsޏ( mA:ud$*&1 TB0}~\^77IBp'vn)zɦ~iQ0gA-,-V榧+hmQJߎr>uȥ ,!ȒQ&Ijt6B8iF纞1D3Gjm)4K0Yܚ[O1k3g~,KEsN=p3' C2 b[ 19:KH5mfE N %VtãʴzJXTz0+I(SFら Xԉ]_ /yk޽Ï=ήBgWwjK r6}Xx4fȥHV]N!Z >yz9 `tq&7F$t%0φͷ={JOGG .::F0ȀsEO4~=C0VI•}۶:\VlԨNMWRR{7TI3^J XHcuЖz{zO=pb.D'a#j6[+qr$c@Fɣ~uo޼1;[ޚbcVBJcAWo@ť7߸EO4㦪N!#қ9#<2w]JJt nEj!!@hvA֜# *H(NRZJId7lC AeBۄ5Ux@SAw+?}nO%]\Z Ce,0J0\ ,A4G2,=Cc@m{Ncֶ1 `H `ydd|rfL1gsT,J)UƈXs"04G&ctubL=gsYVis&**I6?楿3&@ڐ &sdvj#{{J-cbkI UPUm`qDm }a'Vs!%nԓJV B~#J|pqYGf|braaJu@=Q jK [9s`bLxgXkdmV8g5"JA~)'wtH 8˘/TSs.^j,FxGSq qkD17prGFkk=ԓooa{n./WZ`\][7]vt MM~՟.;uK֍ FB =tTw;)d{ب'/ޞ 豃C'Oq|audж zCK GO}XKAZdR!4j .̮s-Z{\NǏTRG@'hlJL$HaЎzdM)#iXR$=w1X=?啹ŅZ^_Zc۲GFF=68kIDHgjrvrr^]J%%}}_T/^Do~wP Gn1 ؍Rp!6g(R/cŠF;"dm"cTj10ep)w|Xj+ !!kFƘf'єޠS&Ȕ2EP[^\;:{ k3&R1ɐc;MhK }O?,&E)% J҂HfyJ%T _~PhXJZc( X>gj%TYHm(#4zGgR }Mn4V w.g3J遁C=ZV֪+c +rGG`T;Xo4Eҫk1ݽ{g-8XOY[^]4{UYwg3^>2T:uh~~˯[N,-,+-  b|:h55]Ṇ8.S!J}qնy)G[[JJ[wDҕ'SSQnߞb( D䀎`纹LT8GOϜ={rGp* ö`">ŔfH(w%h/},nw>(w0ђQ2;kg2G?ϕVhtD;&f"mڧ@0>DQqSR h42aexMy!2JҨ?t/^4k/޸M8E6[D};0gϾn)I")57o`h5:Α>7@W \{F+g-:nO_=2P#Ak0y9t;]>yy( P8qx+jRju׆d K@#vAiBiߵ >t͆NQ;З[vMKːo8i[dMAθAd!uʚ6>Ro&ŀ!p LM0" f{{ `G/.IaU[QW0C<-5}uA Bd:@s/Zf 0L(yY( Zw88Ibk@ 6 [Ęr㜁21"zZ͖ڑ"SpƊxW׳,If }[ͧyG Fz̙}{~_}O?)t۳gh+?ywD>|`VxkE)idXvV0YIDL0'ӳ/\\XdH@(@(=g֮f)?8NXI!*c,shC1ٴD;ڄK@qqC :1fK?}#{y7204aaiwU\p<_,۷ ⚬;`Ygw{7:-W!ƆگzɼKLaȍ%e 8quH??r`UR./.7{\rfLh3ۍz}iania. V}mu~Nvw9on0S!S"X)96(i#~(oӭX`(etDdһ%ckMή2ȶ%S;t>LW0dن'Or# JɴC` X ;6n%ǁ ?W|Gk"Ņg+=O,e[u01.l#LR77>N4 <mZzU=fpĚJ:37k3rh"\,i-3:ohT,"jV,-,+r`(:}HMI#ەwÖ[g+z\`Q?2%$Bb.! zQNvwg|K/ŮryΩ׮#{ZCF vݠ 3!d@FFukVklpĉq2:@:!zW8%rشvT>J#4)5Ic>{-= Z{`9F6jj$P1U IfVK+#OKT\tnz&Qm3 w& -A;<pڞV1ѳo`qZeMLqemiJC83h겐2(ί ^}j) oBуr)xc(6JYZ\qƵ˗e(@10J0CG}-'!^;jska3Y A/fM9e R2[8+}#ΊOQkW_zj5ުkB O$eɂz޽gSQ 䄭P3;:EZR&%1))q. Jl֬U[qDƐR "DaBȹQ< SC ﮜyGfH{nt}Dڪ 񕥵yD՞'z=W(Pr7c?6Dz Iim Yb,ı}7$bI:07zq-ڍH &99Tx75CΚ-b kWn/iR4G\(k鵥RX-*+z K |ܙ: bjj(JT\F$&oVeV݈܉Ugpo'b! d9'mAxvW0=S]Z}嗯M' QjS 33Ĕ*RQfZj>bll,V}rY"k(RjVB7 vL5)0Ϳ ѭ@m7ޫtF2"0S˪4dDZ։e*, 7mL 1#~=I#mn"WX@ḾjW3 FZY_'@ bb5&F[0O8| @$zAL;.zG54 Z;(Ը1js>u]Z9# ӓ˕(VZZwlgnvn5n.w5Zikhe @j jƁ ?bii\.y^V猫$I=$<ǩUKK ?~ZA`8ĈV5ѱG?کc6Zzjkbuf2b F@眊XdhwZf`#2؛sO^9^~CƑ1R/==-,jr Df-H鷚qY^VK֛Km2ր0oE^80`=a^oGϟp.SO>De"àe&R\iy5,w-EFI T][Y3?]}kys tGqPI74VLt xg0r{-.D&ł5V)h6z]Z CA:111&emt׭\鱾4-6YfWaW.Z\Xٿ=lo_QVq͒9dLo\a2^a"!Ҏ#Zs/fjD;|K_zVJ8dcJł8JM9d+NnKlޝ,2y֑HsFkkak9YXGָĭfx1/EDc5&_ +m厭p1""s~c;9}6̾=~TӿLR"0?aGgO ($IZ%)ucM=鸮"5Dd–8sե^~eZE[a  Gӧ#_IA+BZ"s?~" p-)Kts Z8Ed(XMw\0q8I VtK sZ=}1!s&Iȷw9A:vV7CCAK7wFs^mګ13='&f""2J[7ˎ,kX&2d@B. r j8cϟ<6V.'tB=37gB_Y=QZ_E:J̳.ˀv$ lBVDV3aZqBjhHQEƅ`h %%jGv<"kL#=* 3GNۗ$Mz.d<'S$6&aL4h&i^ z."d258s^$j^`Kkё/DMF`_ q{|7>AF]G:B`[%!gG8uu ~#{_x́s.,.sЁfgglĉ y8N#LU=}\fVY]j:Kʚȩ{Gz\7TqL6+ Ȣ[oy>l%qd~Oo؁}#ҕZjtm54$9GD ]rfU[#S}n|vONh;l3\Bjܡ|-[qBA91[mJV#pVE2@]+40Ƅ?1>{,C7-/z^vtp3<1ӗVPIj% Z628Y* I"2֦F;iC#XkGZQDDa+M\0}zkJfhV9SiCJiPC $@c .'<~;9ogwf]Z^%|viyqvZIz'Mis2YQjkmfwi2YkC m5FzHJG)[F$QW"I8NꍺR!Rd3A:: 1=a!?[q'8{#)%?`]Ck/<$fnޜb)bY>u?aزZ$VI|ܹBX[4VQ[nǎtɗ,x+|/`}6t` yhxS"`R$&@[@Ɯ5 WObGO3P7WL&Y KiF7U? 7B=Z[V<̬;w^oJ{МCuP]<0;ԛY^R}5I,{79/r % URI$&5pu}?>nߧOkQ++Kad-87nN&CO?xT0HTNiL$u!G܊#"$"sb`G7ED0j߼'hoitؐ@ULcЊFOJ:㜬2Z3oc _"ZC0$8pMn5(j_R`xX&BXhkZf+S33Jua@9kuuȃyxqDl6らcD)Q ]' Bo_aLvu8?3y}f\q@Lͮ۵`~~'^ܜ훗{k\ro 7z[;4<`ݸ/<}Zv[.[tV֨35kO;QqՌ}C(;(O_q㦎Hg߾J)U纎<dy]]]\&Q&kZkXq%4s& hops|Z;|xW%)Y*sJhCB=g GxwS8 kJԍF(O_%`Cn XOv01k&?d={_9l4z{zLVu]O+uU" Jш0 #cm|Xr=}>J9{bbay2" Ypbz. %kƍPD-{Y4--҅bK:2(=#Rqqe>)>`8[t hew[eLhA' 2xv"*@Qj ævѤF[|A%mJ`W]K`3Wg3.<7X][sk+篆Fbi`39re]=cF<'u;7n6kw/]x^=i5ƚ޾.ϕa?g?,˒@9WӤwX\>HD{{ 0nw}+OaQ,32MD(2"g3+Ykfw7[nr iCYжRaA86nΉvS//{ k?V@9N,RHAOo0lJ6m*1t Kw!p$Tjq. iJ*.J$1Ԥu "kO`9⽇PRDam~=%CąW]Q(j=z?y{*w1;3SU+N#cS! p&MS̆2+4 3mT|;5JX@ 08fzSuV]wSF̕g$e|[~9\te 3s AtS}爁2#^m:9Y,\rg\zqsSq:jsu1g.]tzadTp$a݋ B`p7Yl'a T /_YYi᎝[`lXB`!f[)cj_ySB}/XIMvِ $B[ #-ӳKקVkPزwxPκJصA{uㅼw/7pk}2"c1owߩl"J 1nRW[mgY LNN&F&'kDfwrwUʛ=@#rL9VOõZ{||/II>O<_b:DBܬ4H>I>Uae-8o8ጬu!5ij)%B$f`qA p&\ |g>tۺe((Ilye3H/Z2>RmL A[_s[ )pa]gJ3Ν={Ν;GG b\X(ؿ p^rZ6LKTl봊Z7} 6rLL}Ywܩٍm_mVVG>lwΜ9}剉-Zsliaڥ ƚ}cd' N16dGz||pbb NaϜv-gHF1p}1;A\ ,3/\<\enLnl:7'_޵Hl1M;Q255ut՛v+[)U 8Igݜ>ǐ3o&Xsrucd~,:?u:ܹ299e|}; `bHD ?&w%7c:g-L ɳѵzGwNZ[]:zgw\)< 9$@ sm 0~x?/|r׎;kf{pleւeNݿo{9mܵsl=w17d/ sB"Bqę/͵PZIڱo}ˀTh ۤ/ ;Z8L'6'Μ;z4!lݶuaqepԉ\8ׯ DR)mͱcDI$uD( F1cZk!\[W;s=>dZ6 ]fneH(˫/+++zfl$IrW^~5M~$ P "lQ6]$cR]Z?O!cccHj|#G^gesiiz@F[{'N}#BQhu:IwUZ]iID ǯ'1R$q+W^N"H,J4:U10 A/__;f8}aZhH!T׌֌umJp¥J)W,ܸhݍĎ5~T J7a#K|#(Ȭ"O$ڛO4:8ã>t1[Xk˄[)Apܽ>+Rj7rc$eB xL;_`ٱh"V;d$BHg:$ܡ&B=xp~N;xJӳ sX#@r z9 _x_yuu|n4֎}NnT*HE~dY@I oQܦ\7 XH8yf\ !6}f,7Wc. 7Zĉ3 BXfb[{B޵ ssׯ;{frd'j&}G3v|WZC.\wb]Ο=s﫾޾Fя~ƍgνڑ'y4`mka^(A[LJ+U[^>uT߿؂0I&% uӛfcs eH~2wܵ庠`m9)N+_PwoAL*Fpne~]yh0._Z8?ݥ3@sI;I1I ‡+03KrƺsÒ(X'w(BzIl|x۫ c<4w`]&$iW'pۈ@\qPxus_ LJ{k rT*V˾Ga34P<lbm ή2]Bz[m! ƆWVjb>Vp Ih'ٺT;@R>'9o}g|$"VzRXg'8Ois0h;_zȩ%p¹o߶e˫okpB $Z':T]}kFJXI7foܘ@RieO=E11V*-܀WcX%.KuuS[t `xs#*.έ~K_n>rn`% `JcLYkWokNV"`k\֜C1c~ȆZ%zsK# 1B}k7qXD¡EYD( p65N "cRjd$7D['( Y1`nVR6@P18ڼ{kaNB3 rR斗j׮]X[m~K]oPHTOnrb_j~΁(jα}=)1?42>K ^9ro׻w|ra}kׯ_~Z)rg>N([]^hϞ=SOT*ŒkXkϓΛ$o_ M*L8:{ª /(nm>,A-m70ol0N ?3|c#})0NcR` 22~xёca1$jn+ 䇭uDDۂi'"4+3/|%))ᩧjk G\NBצtS(w?hY'<#64R";$?W[Zyf$e@$R 5>>`$0R.w)@)w{ ]]80PĎ}ְ~Wf\.]|mJ0SyA<#}#=eX!1#wh1@TH>HS>i5ggxJik}jXʅRБ_㝣 Xu"ˎ7`Pn[ F 0tynuE)e߲uHXHJ VzwMݘαN%&s)[5m4拗gxR<0Gq\,zB{ٕՕbASqF ^Abڲa 3 dlXkFG}!TSSS/\HdfnvV_Z_7ƬKAh@! vEÁ6I7,1lkڎmsʟ_Z"rFgkJQv;jwZ[rROQI_H脳k2SY$$ 1YynA#)Ej%T$I ܘP qFX, :}3BҖ-Bami sA\EQ9&KI IBdfP   CpڹǏkR̓O9[-!X$ [@D_"^!PZQ!+B/I:e3n`qҵfYM9<6&2`DܨƘuBBl#V9e)89F[IϤeiXЮ7o|s?t,{]\Z2#o;zBIE$ݸ>77޸pKWyLguuh|s?jeucչ);>>>qM nw 5 )MH D@`A$֣o6@72g6& q(iu?ƛo:~G/\K/>}JrxW._];@=}ZLVٸ7T}DVϞ9~T' t*p 28}N53ljH8 <,_NVW.//%UEQ$1C%M#50`TA.M*ȁV3vmV8K[l%}=\8W޾exrY Tq]f'uŻsw}&7V$6Y\]=R!_+B?UB33ss/\HR+DiH(CEdrcGHz-Z]@5%(<46":Z[mtv6{tQ3}GWFGۭkoVnEq܉V"nc ۰{qA0 s<[ʥ/fM(dO8.>cãDJ Ļ#6= )f',g"I: `1[D1i+b`poi0sn \+Tj;V@o&ŲiԄy`L܉|`)ymuuu 7;|SRC ;vN5ʱܲtV*(o`0NVV*JYc,0:p0q ζv6^bܥ]x[ou:mngrUܳw=/yोFFk_W|JDH_}hxNGپ}܍\>|'z*m['uX"pYDS"/q%|IsfPw]PyB,e.˵;7z=|ex޴-9 Θqurbxn~ĉwF[BX\\Tڭξsm&sO>dU|֭=‚MSǏ_>icc[_lVVWR.P)dԗVm0; ٟ";ǖ3 R$qpR>0FduF.bSru?(j֥'It'/NHJs| Ox7 R2<9ex=|Kk%: 0SOw޿bdP(}!>ǝEf ÏNܺX(,Z*%#A)[Ybm[$:@,˃I+AL1;S_\XZXNZ[^jkJM:@$6|KMwu)B)PkiעVE1߷o֭6Ձ**zp`hd(Igͧ#`%xB ,p"(J(7d "B$ֲc V93XnPK?Жqb lSS/\tj,'A:ܲ(|MB瀅 oQYk6ɾ lҭcq X H7 2F۠q,PJI!7[ΡEC52SBPcE.5N;5b4vl體RX_ʕb%~QCzRo_~ %k7#% i,/iy䑟T{ʈsloo&+!2;FL_*W"l4ζ:QaN4gL>[Xkz} 79/D66&i6Ɓ4ՀNSIP*xmݷ- qb|.,X_7_fKW^旒f-8` ꯾bI#>>MMVd+* v6N[KIb.I+VE͇w@`2I`7&Dʥk_ԪJ_RX,06 C6߻sgRo_;j:X(G_0;7cSOOD_F׎c$*{wZz }VfΝ;+E{ϖǟy8TP$&-3G l-@kn\-<$ū'O\ Bq{&$]f z7o;XIZo^:,Vե^=AOjJymڮ%"ZDG(<FfB1F)+RfgyJHD2`)HFIH23A+NnKN^;uX:9u!JOiA!煁KkfZj2p]>,؜A`s.IH aQ9X:yի##i֛joRKurcldH Al/ g㘛:i~~4  2hl6٘ 61̎fbqrb":ΩǮ\DDQ/,)@B;yA@6s`B eBc]PzlaV+ bqEܶ}m4GƘZNHx:604Ry5+ܙgN__{JoOT)c=RMM7Ip 06M<)6(qI{GgWVjZnjd$n+C{6W+M<ú)#*k5ʞ7ͷ0lԝ>%h]b4FA¤9"b<`t ~~?c-*s.N]lv.^rT!_JS{8I aEFkQW_M҈|pٹj' sNV\X@h 9uxX7 .ɇ7ts|*YqАH9v/}kv @~i DSI)R]G{B "jѓ:RoSO>XŅ%É21mSA@r|M[Q'Z] 11u.YRq_@!"NY,ߙZm}k"!A C6+ 0ud.$%:U2MիפR@i| [? sorݻW-/JkK=19[ Mt&EX3fg-"f`!ClBQ'jwfSwbDqjmDq aWtjn 5Y}_-!:yz~n!„HZ`OjQÎRdWH!iĺ)2uRym̒ &]2%eO_xr7]m-VJs-#fFy PRxw5dDR$F^j)?sLR:! ɋ.,5;; ;fkSO=4W~ 5ٸ `A^]zǬq}{\cJkS+pG47l52M. IuR<}m䱋/,&:T*cvlyЍN_<޼v=E+LSijF *m(;V[@ $Ty- ['z==cCc\ۿc1ŋJ qʕN}sQBJ߾\(4:RF{̛7㛖Cx$I~ND]\XiJ~w aATRu>r̷pp֍c& ,$PW_=wbTZJ3O:0+ҧJow{/$mN꬧=B{3[h|gΦ?ADL!0Jcyaiwm˿`ee񹹅c~;$xMm?Ƙ gF}8o}녗_x`Zo) /}%$G'Iq~i>cB"h벤36 ݓ}]#![4:A J,D,~˟2"9F!r\ 9GB|ٚq#M _|XWjw: DO7!Ɉ`B(RHXص#v;MR4RxDRrLea!H"HѨjٙ]s) <%d]R[!Ie-:\Z>vTBnhp'c==R%3i@y[k?b#\;݁6QT(*14{ޞJq=L߮Y -v] ,lǪzkfkn~ē)qQiap$0n=o%JP`Ѥ<@gt+Ν8֛J8J"e25 67=͓v$]cy8{ci<}R N ʀ7iQu;Dy̜Tʊ3Z )[o=37j}g~w/}+v[^^<}}V}pRԵvuڝݸ|\>?@DeclV_][p[d`r9qo\) 1K%\;w\Q]/rR._ e}ϾJ@Xm6}}UPuzt|HԀL'$ ZX\}\$I),P> B9/ˬXpu+.e); H@m>VNّ(4rְ)F$T͹7|`~xwwֽ)2¬QdfA\,`cR&H:. x^:7޹|ZKvaX~9we;o^O6xJt230JuRJHy/}k׮j@/QYfCDJ~CΙ_1_//~r'<G Nwݝ ;|I>g~7f^.['|9t McDQܿogiI{t ʁ0;d03pp3Ǖ}* ۬{;֌poL/o߄ۤNgٜJO ­Ha$cѨ% Z-[|izr|4nvl۵cG$D+OܒtU+B#BګHp}nfjn&jFB~Ϟ#R8$I t2Sѿj!0E ,HX5;lF'V!vOz  e ng+Hƻw^h5D)$.$D6']ZZmFFBZ֍K:H0Q ?:Uvy_={ŵo? gO?G~~_7?D?gnĖ-vl'):QCU>c#}7>1_|S*KVXLBNǤƆ*=pq~KaϮm[' uʥj>NMRd>Zk(QyQnW.X^y{h'PJa5XK6fJ0d+ R 6oqkc[r=χlά:51*&F @O"JrxjJ34GByS]i4VR[uCg%!NB*NZL/q"=Dt4mh@Pݳlxt$<2=pc{#>r©} ""F#Wȃ1Ԓ񉵹T K2!:g5:M8e\fD?V*u)BYoeI%AbS5}𫯽_vq/,-X|z{zѹ$ j+%uցu9*כ;\. i:844>>~ b'4ilz)HJ:guzT*{T*);r~ƒ\k#?n%$P6뱒>[G|vSt(Pjs.\靜|q sKs7fX^ b~rR n0 n7+ݥfY"|!|Ol6; 2$VdRtҔBT>6amZjE׮_MGٵ(%0|LDfF=BcDvH 3lzAڏ|ťڋ}!fj+(N]o֬7;ɟڽ#nض} )EV4 AF~(pcf "`ƌeϗF[ǖLDxӶ`H"h 3ͬu٤x30[)dqĭV\[k}k â~4eLS ^1Y眓y ! Z_(p{ܿ/~/Rؽg;'N\~\VxɁfuw^yؾSWg05ѮjqF ѱmi!Hmbzuy7G`2SRzV[T˅K lYfC׽S}҉xsK^͑z<>%[zo6|p&6RHo*ewk!"Py$;U6 )~45=Gk1X'-<'MDf7'7-~ؼ]`r $D}|WjǶ9iti0J*ae ~Z'Kv3?VC6ú{Uz &'ګG&P Y/K@4[k+kB෗Nkii'!M5dv 89.( 6xӁ{zzJRA$Q|V.KNqy㭷,p Ei,|w\>տ:} s^x?:x߯os|.3O??ZooMNL·~?ɉRO}:k0!f\.زmY+Y][5j5N'MSc50܃.ZxQ!_ qZ &5i)i7-!]L$7PoDRJjӧN )s|_oxOOXAGd3)?IKQ&q. #E%}Gs|8<2 sZ{iО~l5I-͵[C?Iꁌxφn-3`dKDlYF]B $1Qt:J 4E wZ(@4sssgΜ1P*WwܹuF^*6Vi"2Z45ڮ08Mڻwo}S7_۾}׿.\Ev;y)kـ]Y\ 6 PGWqfJ6::tB.OH଎5:/fcR7C.;7޻;!8NӘrN4:(i9߁lV,_{qPnUˎ.,{!3~@F}|rd-);$$fGN+mVT#/}qan+GΟ?C,,̿k_F577f'~mˋkyD< ZY];پų{-=VG 'l ĖBqZJy3"ߺ[ 'MMe5zddСIP D 2*dPr]VDFte׏7r!\(hLI17RPK r9@Ǜrk[ ]m;$ddN 2-I:giST"|?G;{{[var ّ̖P86^?ֺ|ywXyq}ؙ+bD{M3iuuw}BejnuANx@Y&yӔ35 l-2C|~8N+RҜ]veZvxh~Vzs~hܿvNRQL77s׶ݾmbvvT* ܷחiGQ"'=66M5J_Z+ȴ(f6Nx=P20.o9J[ IRBzeí&(c!8`gR|k"̀DYK*ppokSW3OIb@Yw*%}^[YzWk]vjm.^P*CC}Ӊ'& }_/e\|GFƮ\˯Ωᑡ5IޜKM4;<(vU) _l7pb5YUJN$ ^p;GOb%VHɻwOH&'FFGA $m;f)2 q+Nw ]"wueipA!Di,-78=;;'jkfޝoy% $חZ@)0lCXo Vq{&G_L@}~}3Fvl R 9v>?LGΆ,oA,km!0EQU%3/1CZٿ޾b D}zK^:l;uˢBpY&/f~8=Q$#`!'I+˫ǎu=9 =ha֦FOJRckZz^2^t:1 E s[_VܥKJOIf` 0nx]o g |R((ʅaoOo\6֔K\._TrF#kK :5BR}/+g޼ٹ~{֭CBJm 3ߘ<䓃_/|hlt싿ŏ}v?ZoVZ?g}w?K/p3@V>!5Mb4ZRJ("TTJ !Zv{sR [<* rLV+e3즖,ccQbKKRiii-,/ vAy t)V00mкUf`m4qrٙ([f^۹s=󅂳VH2F}呠~: p:gR|o6(V+BNq>j')b_O36E⹅˯gGA(ce `!m 22g? Z\\t+u,IO8i'9y*NZ?- /_tý8Vس0_P6TRGZ;Dr6@wmxe73v&mmђʸrP %II!F" l l'=vwxݳj֐^掼⥷>&[!;vQfz #/WˋsO>v/g>h44˕ r퉧ڵkw\>8s'#׮NKa y91\ =uܹ٩kSho_o!ke!Ȃsd]uurIb@RoYk--ͤXdߗCfH4lG-vY=yg?޼ [c)qŋS.Mö\.ٹ5̇]$pMo܅$D@BHEJIR\oiajf!Pc` 3KqkRn7ˑ Z[@);-Nnr82{HRZp9sSn-MU{'([oR )- " &mڞCyrc~nh//Jekd{;v9:BFX[s߷}6V|߷g_OJBsB !RZb+!$ A䬕R*OcMX 0̤iZkԯ]V.\tQ[ lxtysw3/X֙z~}nJ2dJ4apH*mAQ1@IAh3_AH7Ix,wu!Y,҄SNI% R\*պ|6kkz#G^=x}(6ʓN'$ w9ר;,s4[ɦf NL j=V^V^RTP#`tضeeR*Wz&*&.#βӜe1 5Zxh88rg/WV/𝨓ΜpEǸV^( pt|vzsSWvoٵ}T)4,j4(_̽m0ySDc"ue]6tՀ61nwZq ZR͆Y_;vo S_d("ro:Obhh;GSV||>ٯF>GN>wwܹMGn}nC&i5@OPO.TJ&K#"T^9鼾$fVĎ(|aey&ynddpέ~j/*(>R]Vk$˭Vjk-DF$71#*:&ur=5$1! B%W6?c?n:x 3!*̸yؑRy$٥驅VE탇~h<'kl&6w+} ͂(uQy6ZnaKs~j֚b dBzsu+Z&vyyuϞO>8 o00`V Nc,{Ӆ+s3K?}RڵZK_tqv~3ЌhQ'bƙZ3dl%޼<@1,( @*sN$P@>%:M5kw`#8vjwϾg}X(,/>|ĉƘ??_4M֯TO:$ɣ?2eB iFIPFQ [-)p̭z!"%iJDNG*e)J|gJaA(:>)FW{GGG=ߟ__ZZJD7n4w}-8HH$s}XpA4.]7}B8wxTf$IlWLf5"B6)g s<;p5gf(kRTX$qlV[]Y\*s<0&i[J$-M:+JTJ o,eqXg#y'zldбs]Bf Y\l Yo-s=1`9\x={r`M]ľbb!a5خN~9~$/XTFFG.^h4~8IFF|󭞞W/9V_ Cs,.ͬ_߽mPʕpanFO{ll].X(DxFusȼinZ^rv+#=jvzw,kإ}i `&FaXދo,.?@O rDiŎb4|J,J0ωG&@6( \8p{/|1$y~?scbZgy`w[}0@ `#@$p|+wk%L$PI$D"""k t @4IAJQBi3u6cZR&ZkSVaA&Kko}j-[ՠ +Q|ϡZX[\.Rp.=}alc'4MSzC&v_6Vw^=wԜp9:HH;dg3~"I(`b822R㨿?Qpi T'S%ٹhAb$DȜ4ޞtrBB.^xAD?g_}#O|W~WO<[3}{ܛoqz˿+c }K_z啗}sI"; 8-@~0>>rC1)z:"# c9\ t;w"b }oZ)ܶMy"y:yץRFv֤@a0uPX8F D`p-^9`=%Oŕ45S3/]~#_ZtAz~M|O_zуS/-{]fVHmvN'I"AKSHB& gqƭ,5JQWd*LZP\hN 3F~hq 7qY&c:u SWfB߲~M))۶BԊ}]vEQ{fW?F .knw^{}:tԩS;w}QҲ=vb|l][Ν956-E7R UC2ۆbbK58i1Q&toB8&X(l6n\_[m A9;^xRsMD)+6"Ç;%3 l IOkx OXLS$z۶޽sI'ud-s, !"!K"!9*SNꎕm:fD$,iF3݇ڀ%e'¤Nd! ZVEQMy@ym 68f-ٿk̤$")*`]fD):&՞đuVIEA*BNҔ]כjX۝8z{\7&1V.Lb1!$h'Um;KZxn.]џ/ro7Ⱦۥ|0h]okK%(PtiH$9 Sphx\PH4ѱ1g-CNYKHYs7{"Aj48n5}}} p)Q`-2;9kbopʕFxΞ?'Z֙sgZEKKKvgaaO>kǏj&DN ! :Q{$IuBHpPRN sh$|NHɤmΝ?iv'5Ȕ)Ӂcu1N͉ 6ڱyׯ]{衇}'o~񱉾TDŽr}Ɏacm0 ! #`׶v_h //CYeZ}өo:6<܏&Z4(rIlɌB(B*H:m٠"x=Z? A_|ȑ°'fyyeaa&鳗 Ok>vܩO|? v}{Y=$Ŗ5gl"rβCTD ),POP!!#/Y[- /_>}+C+SS{zz._ٳwO@+>Сr1vLnVc?6w$- J-2Y,u΂0 '=;g_[]i n۶dؐ+QwZ]>q"c5J}rp\*yƴYۥຆ$;!`s @ÖgGHg !,ݔ^\I pd%m 4BHjej&D+F9gώw+s_X:RJntkuNa&3kUM$I0DA$k R)~XD M1F)2iRx(|A\k 'LY9i,Nƃ~h!|? fgfR A)GE[QaiF*+̐e:\-Z^믽wߙD;ӳWxr|l>lnn1K˯Fsą7|91?=;_k[f0^_'gv Ak{%*RP.ش?t~ V?|<Ћ$:vv[loZ"+On4֦~[cTHOr9vu?RҜ 9{wwe n^<8[85"v.Y\PKda$҂ @y Ef~D/Q ~ ?Vu?\a5&Bӆ>='l_G|B.yKB΂]]ٜH%`jyIDxԤ)=f3*C#?O=n9$IJ " =c%H*i(2KMgm\ɴVJy#5AwgX$i]!gOF!6x][Zy*m7w|=u;~hN)߸qӉB1|)VY(BOz&M3tZp^0 ff>[;;;W* Nx5}桰5$9vI)(rR,˄pRljngXNM3@1Pf`IC8cv<0 |̩3r9 C)  "x:f0˲hď6|(e16MSAnB!x3ڄa~mcc}s#3Y?  P@dY9`Dt :I֍N ufkX,|UL{5#1CApq.JՍͷ|Zg-Q4R)$2/{ .o]\}pk6F:)6CPDj˜N1vUZ9fz r:&_+bV~{j}{{{mcݴnir/YZ]]߻pLzJx:Ky.(ˇCbN؃>64vI c)qSGοᵙ=p>woZ_z B̎]Ѣs<;;;oܸq|eO>yeb|lk{ѽ{Z!s=/޹s+_y!Vk`zzXM)\rI+]T**n˗O4kI$.&? \^P/;:\lYi}m3!噳 ϟt9ZBL;4<# hM|(>}WCCsB `@ưnB̀3ΟNNת3}L;a֏Gt x~4:IS8Im-[ o GG fxs^_*D1'ƿ/ QVS[Hu{g O %Z vf! kb ܬ6Jb1 6Jsϝ?{n|W799zͩR$,[y;9:#ōyB] i:gA(A'!Z(tm7nH%6,dAvdFe+XODƚR4::֨7͛!T̀ OL 9k22"B1;ޞ 1ucL<Z`HH@ {]`-0ۑrif277>6!UND`'J` Pݸ뇅ue^*>Ss';׮iǙMb FMmVP-nn` R*Azon}'&.\<_)l#8 bvHCEna9Ip8# XP71[Ry_wݻ(=$޹}t'ff^{nzono_yɓsH::URZ1JtlfVX"L:Xt1;:r0H?V!:ZFҰ` ?ӗ~v `h$ߜ^_;g#b/7ϟ;οO?[o;oOO;{Ng/77o\8UkBkogeuWӤ#8#koHёJ0W*$D/3Te` 2Fv~GBzIl 46+kׯltZSSEe@:epRInjy𣀱O 9GKΏvtȌ.ghbۭ҄duuHAȬOLMOV #8vCW#և=|:LO %elQ, #P\?b = cF* 9| б^ő,DpYW~J'=X>ݑ+),u7HSs; ř k9E>Ӈ>zaҹ,̙BxX 9Dڨ0'"&D)*Jܤiow JyD* H}?~Y(JA]X޹+W/O64&A o ;_ݽv9ُ@o/X_y18,XvZ WH-; sdsbz\a"b\vN{$֞y#j1ڼMq?/0z^Qst99sp! ^ecY:ˏ&I)4Z~GD!"{}PJR\Z\]Z\e SP\O=;33OM;X~ 4:s1%6$ABgǓCg>n%;3BIq*#VH&PWW{A JYCB8Dz橅^T(L?yIwt{{{|JX|JoEnVn{q\/wjϟ}angsm~Fjs3slJ>2jG-z;9a0Gbq޵wߑkFǚ?R$=C!ȁϖDdQy~8`RA@_]۽ousoK$7Oϝ$aRQ|7i)"Hz Z`^8W<6G0'1O3 Ȳ%O q2,Ey-ck҃uF _*/ޗ2@β$I>H!8f!M",P.W5ڼPJ9vc)H%IA(N(~&jx8)AZ`\(z=AaXRJ>u  pteWRkׯm2Mu֬t22ZAdzVJ^pb|b9]jei%βmc Dj<=ۨTJIڗf" ,.-.--ahV<\ ETYMs6cލ6i?~=~kOSc6/^݅ kKشe9HZfѝbKIHGo((! ObiC|s!l`;K+KʈNj$Ol @kg6uDZD DP[mցCDsNŠ~w=Tb4-5/ld2 nz/~9 l{݃n潱ŋ /|9vwvt066nF`h1;;7á=?@bsk G|Om0 eky+K{w ai© 媪(1[dv?g1 B?ұ~ncNX LX-!J cCC^<^̄VgA> I@R v_@\% rqQ50'WVEJwo޸~'H |WCM; %6?EhJy'$"fVk!$rAP!8``{{K`,묵Z*R)̴RŃKR5x+37?s+/Zsbdw7f*Ne/l'IvQ@kITL^Ԩ6N]&m5N AT 1Eg쥩BUEevyH K QA쬱Z%ag^I+1&38njyKt0|EyH5vJUBJAi\FœÖ, MS5$|l򰽵 ⽽VeYXVkU5 CH㐼W,=:hyأzh0@H?=T&e $uJ$IkO S' o0d9kuqYf$XfMg.@ew~w] O;{vwwossR\z)=ǶX28kTvvkڅbavnlr,QZcYAXB)I^XJ?,No;$W^ @ih?Ms wQX8P ȤG5J56*$YjmA[1i^pv|9=;9;7޻ʎkUWJ`LTPQm x v:h- o;O g}Kۍzwݺ姟yɹB$8 쌄GZq6K5BJ= T7r'FQ姳K/[mkFg?h,~w3<R%R,C>rN "VP>`'}9vDEܹsvqNq,o@\R*DQ!*T(RR#5O% r*OYg1Zd$R*)6%K,ŏg.Т1HIWRw -[N~w;Vx#^ k#5ʳc/|>[w}* Đ8pZ s0׺ɹr3'6H@DYy R$F7nR8==:Q.'g𵯾,K ZDhp0"Z [k0Q' [('_~ᅯ=Kٌ+?~iv:q ,Qv^S?ڭ gOw7~O<[E>XYg 0mux2t)P$OL-O  @0|_yð3vl@ƙ)o(Gj#NWx~;;/^~g677ܾ]*{{SŠI<3oo׿{z}v SVkw WV+J~geVT.J\ X2I Eށ! b2oaiOZ'yR+5%)CPG1k D hs mIxZʅ#! xB:fI)"H]OkP u58A@3!駩ed}fv||<6Q#ɀ O+;?6I#DBH$ %wnvRͱsݽͭɩѱ O褠Orecc+j/\26>!8vi7c'i R U &3 ji 79>A//A,qr8z`7Nu&ҙNS,;ȁaGmc> =DzDY< BZ ӹ 1OA$i%i: |e[R)u$"Dxz`,mw:vss',yO^;a$!3=4cfd@ VWW]e!a^dX2*0*Q!RRFP, Q$ $HKكJH2䴇<90$ZG2|ly|G>Γ6B!j3SuASNϟ߿7 V7w3:cDF$)6:齭;[/8G Ù)$ 4676T*yX(̝%Ax HX|ߗBR @Daƃ AY!$NNn93An\q0 Ð Cĩ3z\}uCGgb}84 !2/$~2I3W=yDզ$  %pn@CO ;z:KA8NьՌF@R1RIpgʫ:I-pc;;lZ`&FT3c, !chz1~ qgtCQ¹ĕKWpٜ\A(8z~@gV Oa^4Fo  _[oom ^}uxvjShuox@J̏߹qz^_D FQa9#!7v)-/o{k[ko&E Md`c9h~gXcZccv~7W_p'_~s333a/壼ŧUݸ0}zo[=m6c\~9aVG< ADh7vwwN9uyh}j ꜱ6&q=qٔ m¢λtF{?BQ5ꥉ5tǰaU-;fR!r |/fo@̋BC`]c%dB :;ov%|S /]>Jzst,Cn+)}f}y0ƶr !'}$Q.UP{{QT,D):$T*βGF^TA%l,]ys=%=غ7ЕxTyחR rM 1HWr^kV\>R+U#cPuƽ6~H!xBD'ǩu.r26ODXg\θx⇤ٱ6r՚#b3p"AlǝNg0IuCQt̙˶v7&;'p떏YRCe<i+! Q kkޖk)8d#54B15$6}ۍ"DJbP(j9 rFaoTDd>Hq:笵HP0جӣ@mAp3:V^G*mnHArҳ( DgZ==?_Rff~׾V/ݾSdӋ;xǀX;eVL꘤NR_xNR(Ro_ <]9=~V$xesS@`ť8:"J-Ρu IyɂOO@HL/|kO9ZXw#`9zRQygvG30kB`pn$;9>m?Ü*D^n{wd O۬T*m`=*ɲ/}b^v+W??ZXo6̎~?&vfNK_r!?Ns3g&I$1Ƥ깓Hl\Okˣ㵳NY`Yg d&`D,ONLUcSZxT~vRvH#6ActrkiN:K33gNOfj͚6K9>ljǶ <~# B"'\p3~4k,Z/{WBK/_$GGPc}$M9 `yqqiiqmH"u8.cnDCգ>~I ds&TOQZ3X 8 a7cXS;6M2No>RY͍͍ѱJ`Qk! d( ~uu:])d\~h`B:8IlP5ζZ;ݶ=jQu*+Iٯ͓3$ˋs٨͝8Qff&:B&6Ct=<_BѣLu;zS\ OPI30i/!! 8ccn(#s-Z.C:yLh|gvp9әT.Tyo|Kf'[&mayucem5IͭAW@ ͜BH_x:(LRdAW,_yR1 tV/Qj%L) 7(9b`pQ b`GDlbO ~7AV׶~W۝}̼mX$ֽ$.ʀ~?#gNK f9dK3Z+_qΝ+7% ߲J!:kQx7{MD/^z_th4&&͛NҥK+++DBӿ^T|嗞}Skk뫫cNdi|izрLoyTNLګUٙQ \@(: 0˻!A{̒{j f~ O>sraJnYyĎ 4㣛k@vBulYkn=*j}<,]vivv=$Y+owgy%1f:ܶd{8=+P.+D&ˀ`gw7hsbff4 DVJԀ&@C͎#?+?2QFSbtΑ籵(É3AAHJpY^] BY=qTVNi@ l>4a0ȹWc$l6t:ADȈ M☈ w:a)!M Y]]gΨieRi-WƟv;*U`*|;?N4 *[BV{/(է.NN6wV4ABX+{ڨ ]/T&Iyf~X)WKr|bR)TG! msH3-Y,*fݍS_~ v7[Dѹb  IKZcH:V˲sNgooS:p0AkKCOQ|-c3g[7޻z_.11Vٓ%J08(RaBvWw{{7]9Z))! w83 \*V0 QA &It%q FXrit:$pl ;ڂ %BJac~e:n|NǞ ܺv}Qig9z( ޗ bIdtttrr9T*w4VHAyY)%:=1 ҂"p Yx!*6 I)4Qi&"e$=B,.//w*Dv DGò,G6piI8((AL(TjFiAnWAX&Z)/w=ϫWbP)x(FgNt{I_|;{ v˫%lnq3,s?V_k7:z9G?h~O^y j,` ]`t:sišHiLJcU`d^ꕯ~,a_x϶:}epaJ9C>xv,#3&>ΓY!x ߹eZkp=F7D`΅pDbx\y]ĕ'>pڵgϜs37+Ws`0,AYHU,nlً;f秦[o]nNTǪpnaT ҃n6ڈ_D;Gpn~60Bo{7{֮R.I'Ϝ0[gHƟ:0q8T|*GbcVcf}R9p)q9(l":`B@Ă+]|}O ǩ3e 9\vp1@aK@`TNA6n'/:waѬdzPxt) IqhBq=]0ι,^}l "űBVưyf\,Nz=)P" ҥx~$mwʵf۷`aWKd٬ %_)i˵Q0pT%u}՝.BƠn_x\ jըTl̪Rve$Z)OP-$T3bg{֭k w>Xe4LBBH!f:s5.4 4M[_2M Xk;p0'ğ۾'nYjn*BTK3q[>yԉKߦo d9ٿvldhTsMR2-$B$4hciCdbp.gs2$j%,J˝͝mt;=^u:{vBfc%X,DX JZHt `$1}MHoUJgmk+NIdklo 3Sk˵U+R125&0av fUkuJ:紳ڲB y>4 0hJvkWTOϏU"eDP붕OY)Z +ȴ[+qD?~镽VKJO;woyfY&HT!qn  3L} 0[إ$w߼4W_ gsSsr%IP-g 2V 9 qGHLwXuT7 qKB⹋EχBp!xIk!*@RB*W<- p0Ji "0_DoOԝ;w_j9:o/]x~㽽o}[_W#_>15Z*7n3=?~qnzzʍw;urzk,LG;?P] s9mhx1c> @@CuK rfo`&z%GC࢏.ϼ{}$8Z "R`A3g R !V{މA+%*rn~V.W^\>@IsbiiիO>ƚ &~@B0"z^,XT+)Õv9% so9)<3NwSδvg'nVF~2g_Nzi7P?R|nJŴ֡CB|BJ% ZN zɠw{x0`nwqA#<@@rH΁E3$e tJj3`I"XkT e:Jhc3-!@P oĀ̜fPF7Խ#77"ru8Ik'ժnAӾWRT*Cw*vg4AȪB9&aI#PƃVT˅(*UH2FxyamRI֏(OL8̌. h!==J9\4MZw9O͛lmiF Μ> WWOMΏAmu/<XdpMwsnA055y` $.Kj𡺿@N)$_{R*~+_8yQ(٦RqHO[@s`%AC/GdRβ``iFP eJD*I|F󠓎nJR^\sDuk({ =3_~J_}=Nzqpp(Tg4I#PZ& r(b 2~`9Xkk-Axr`l̳Ojc)J2g,8!.(&BffB%V>)0D?L0Ȝphx gd}`m{ǁ)dNİ/JBT,O̝X,FAf6I̝; fT.LL"LU&&k.?ȻqK gNwFt=덢KIXC!:woyww _dϝ"kOs, EJ\:-r R wZbl6ƚryMccb2S#Z = P5$4*DGR$BuIbK@Eӽ,ZnpoVkKj-5'!e$3  W^n'Zez?2X} LB!i IMX,*mllJb ^\L΂;Xo68}u,}di4csJ>^xC A3!QDu4WԽ 0}< v(3uҡ'YPar&@gC@L7C%9K[km~? !>XDz( `||4 RE>"x@^9:lTT^( .gO*0,MMGZ>n ǐ^7*pc{ۗtJEkC(HEtc B),3iۭRҸ\N[:5cAkgg4/Cg${aϥilKHBD>,%Up=$>T/gN˕`rrd{ku߁fP`LgXD9ǖ*21/ͮo[Yd> àZ-5+O\V ;r3֘~W*$&n|1sI _[# Fk7ov$V뀄@dQ^`N xX@ Dcn?5 I+OtQp}H(aiA 'K%ZꃕwOOM:w9P0Vl )t{> <40R> *UZ7~}d,Ml/^/#X Bpsy>y _sת[rww칅HiB$D,N-\hGGH9H%I)C74w{$C==Lj fvRxխNBֻvIDQ[LX,t~pz##Q6NeznvVNZ%Iu^}jHfN9g2s[ۄK?ɩщΒsĉZ^Hl 2 "p$5qws}N+WOY8w~.DTժT A Q=k&gI ?έ;ֶW9hTϞj4ˍzd)Cyqy8gj5L tNI]~_OJBa|DG" t~HL^cqw_Rlr5@Ph=͟F)XB!*Zsҏ M<}_I4M+8(UK%Kz~p&"^x^ֶ֖׶66[5;3yB⏍~+I3 Tab|!wDQTGcOJ/LBݸyKOb8R []]}eY^6+b)YV=hwA:жu[~c)@e|xQftp\xBF$zP!pXpqPAL=xI>>>;6EG <&b8ttjf̀ Ytd WKMR≙ӧNlR/ȬHh Eh#A?sLdDH kЏTZ|Is9c2̝wYJ$8 \N4RT*Dw:)Iujq 33^wk'mF&q2 OYBbcDD@ 9YBj|" vccojr>Zg>©WJ,jBgݣg?5geRbB5*@@/RX*+^COHt<=Z: RA#:B[ɻ7l9;R|WCٙ_HwQf,3`xB\ lHsZ B)0n7oJ)=sK##~t]@ g9& Cýcϝ;751q2iv…FS^I{oozwi̳ό-/--.>8sO<119!+/,//~jskw:m%ƚ;/<{eQ]V f1>z h ۷>2ިO^&3`l ȘY # -E`OgvYC@Zػv{;T њKhr` `ϲs@qɑ:_O|$3ďXXI L ܼy1|fh >LRtMQ&Q)Y aE"L}ɧ.MٙP̜&HF16KPHN\p-˸ [^96OݑZevv<(N,fS^jھ :>hq_#Hh˃~*Q*B@Β6n2n7QR!Fc3ru_ M2pH8CIb.q&Idh;>j0ӄX䟍h/٨H2=pT`T?奎R Wo3_ yZscg8D&SiYFJ(AR2d*eQSB,R:HH-3umJф QHPh,ӈ;(cGq^Zk )!]sB-P[Z8T ;߫WG/^̓M?K&j`Yc6~00;N @%BQZkd`3sE8r)˕ #o/KőJqғz4??:5ِ^Z$ ȣ3/N'c#t:gHl;=}fQw:=J8 4=;1Qoj֎;7bc8:3bt"!8gGj= +Νy'b!Ӭa$1;/,Kb@E/'&ͭťecmI0E C a,LoWozTY]Y}مZ3Mjdm HkׂNgi 8TBdvm1Ew+k`O~0`':( sWaQ/_s Q!|'NGQHhc67[;{;KK''Ƙu.IAiZ.t~_ENfqwH8~NS#\#8Cb6w0&`dt]AnivՅ'O fffH| #Vsͪ>$aBFWn?;{o3&(O?sM{/$ϟ?$)PJ`DŽ G5?j`ZVYc#bX/T,CjFAa!I?R[anzxx-G23 "c6ZHB%56RYv;j,<1RHQ ܩg3Í7ᙳ礒V,6{7o;hw+K'ƀGn8lnN=__nf3HimӴgTDJk| @'mT$qzN8Fr@ RR)ǾCp:̻9+S Aa!2unfCӇc넏FO<*8NBFZ BsZ AG}"(%87v9)ɴinnmMLNg{~ w?X磋&,lS$`!kM7XC.1$r.CBIl Fja23"IΟT;lfI*yx262rd0sg&v؋dA^tXvL qgVglY b!(T,<`v;jd@I8yBV<75}wƍ$EY\\qs@h * $Nb-}@ip~${o<'{wJ__xq.zpfa, VJlQ d4f6B8ЩրYXh<霑RcQBҋ^{' 6Ia$bCU^2(HKyklw~7Ĕ勗/RS.W $Q|TuX #z(0Hs4 nǦv3í!c G[DVg0.avY )>?'wS{cN'(ECy5?vX}B$45VB )Ӄv{kkQAZ6(r8|5$$ʬH\*gbr/}β33:ˤNgwݽ]uZzTTmsٰ\bmh=>>y2:SJ($"o(9uDs*E21fڛLY7 iZ,fsN{d$IjhىX  ,[ L L?,eƮmliJxUB:;S ڤ铕J7R,A~*`M)*mxBEIZgf&NT`G V#OA$ I2&&%RZ/n7 ;pRo֦'N+WIֿpKeQ{(%pN*Jʏ#0(~htA8`TDLߺw;H-6 D@Θ,P9`z|u7_ `owl^toܸ˿~vyi5")͑ & (VVVvͺfQ7*`ougsK\ymRq"ZkHSr!e G'*=Q!rΥi9'Ws6HRHJA`3J cw]!

6^QL$o^=Ҩn^ZK?}pSH~JR)]3+*g gM!gA)&g qJ߿/k^F/-,M) #)P3q{Ov kMn2,Gfkkv:zGVF3ssB)(JŅ`pa-[[RTJ8#!r ջkwɍX.;wRˎ<|?pDyA2hxGBQT+iJ4|ɍ8_v3^i_}̤Șv9q&sqa%<0΃`GH(:,(Ͽ{}_T6HBWױRY~xةW+hq#K4Ϭhd_wR"g  8c9( <d<̳;;oN:uݝ$gff9l4zɩk5011yݫ橙yS+ ?ؽXv&#S $IEIb'.f$`w&yK~<}X!c 9_}R0q\_|?[rS*R)L|TOՒ弐0gL ca;$G___ux%pG" c!q ,9d8x*HꓺuZcÁLHqCƐ(2Bn62.8pzaA9Yx0uITj+88i$}|@q_x=zxw:P4FIfR$ G]1֘9RRI)@rioOslY21XDxD6s\rd MnAF>ynmnmy:9u dȕ98l./DeaiM5ioF;Wӣ,'`a"͓QbG6em-]AS)ha&Q0.X9Gi*>>!8:◯ލ}/,I (g)GƐYk4u`c_|We{c =r΍֞RXXXҹ朇Q9?<{oZZיtAcsS^y񻂛ug\=9rQTH$Z$$8L;;GdԞ,\R)``DfW39z<HufG؍[\fzj6ӓ>i䌣c $`~=ue BnYVF>9ieB5E5Aqc#pq^:ꍆRg~gH˕J}QgM D'IF b/ K1nVӟ*˭|ٙ3KaOM5pvJOv4oAf ^Y#bˆ1!%IQR1Ɣ獒DJv=Ĥ(&$+% =LGk֟O Cy4YJzy/%jV qhqL8)p VSVapgUā }OIN3Ur䧣a;[[XƑUL5ɛ6,/Jѹչj9B<,JrY]xZ"lg KE1!`0*,%t82hkk{Q\3>9YΟ? ϟ9}f !Qz "rDd`q$Gjӓ79Dzݷ^r]o6,?l" t<ϑ r9B=OT`gT+ %~fgJ5>w\{m1RRsj2g :gWNOpt/hT)b駟~:,=+J73oONf ߹VO],c)qkٹsg,}^^Q>1^kVd;r""!+̋4u!rxr^?Mlh7Aϝ=2%11ӣyT˹Y vmmkt%<={njq^ ͏ ߛcA :>V?b'G\0c;mDf-T(}vu8RBLKgZ}z~ {;QUJU@zԴ6ؼ>RD䀜1Byt$s7oߺsV9(W+BaYmUJel.+ċ=T$Wu{HF)bB n{ kBs/<} S\0 wtt{zfjzbb v;%yf$yx6NP3p "$N9c=bssgsc h P+6 w5 b9hmomlv:홙_xalrz0xʫ~֘-θfZkgi/y+)_y񅙩SϟO,._}>Lsc*~< 0JFvEz zZ+ S~:MB)K~QbR #g pY%`UO,KQ;۟mnl{^xar|bccR.-.U*_W+++F\.˥n}ΝsgτQ3WɕyѠ=91 ; ŋ?QNLġJ -|#4qI@!d2:~p\xs/rib&7e. "W<1Bq!u:{Re4LHZً/_~ᅧ}O1nrC yc&Έ!ȘP:Їyu.vG(:B/9.!2.00#GD́N` 3!+ s@1\67U!TծPq[q8cȘs/ Z* Pr~GR^U),?^H_ӞB%"TJ_O*=b}8L#PC PR@$J 94Qޣ0(EQkGdO{CrayeVl6ƌr=m+Q%R [oەY:/IVTR{ cpNfox>A7om3*xRIG>rH2 BF`SԳ'*]Ā֨* mo< xb|̛*0fdR5^K3@7֔Q1^3sx>88 ci,T(Ʉ.XeQ]A ιS+ S\l%kNoac8r1)&,%7U) Oouׯutx(\\Zkr8EX*cXDYM@9F@0539JJ<;5Vj5WޛwƍHdȐv綎[G-.qAt; t48t{s.32-Ŕ]JED%s6++4 }|tij: {)!uJ0 oߺ%7 > c *(@DQ'a;k? | :G\c1<Zc6 d9u,(t~,=::C1d yR $RX{~Ri4j‹Zqs!T3 k3/_%ic|Zzjbrو.\X}F]s7yFS :[[_*W 9Gt9c:ޘ$M5T*M$0s:yP\V p CcO5 ֬ǹ' ~"Z:f?V{4e*tAk%2G?ư֏xZF+S3D._z|cPR)JBA d8phrKd=W+G &'(DkR~x$#kƳ/pƍ[7oa0;7/xq [-.,OOaF$b\s.TnFRU=˪Gyr&*p8"c'3 (]Y :޿`}d]\Xj4s߬5J~}s@sxy"w*5Xo> `RQzG>>0Z.Q{!3_3ֈG?ݺ8ueD@11!E.uQ_ '[|!>K - D`Id`,hSp> E2ΐqq0N'ߏ Vk&I%UPb MZJa Ös9ޘJ2inܼu8XRb:qr 4G qmiUVS W'[=,=:N|!G@Ƙ4K =c8)r0 CG9y8??>34zݵ;ŋgj4@iDR0p8yF`6n-/ EX8yR2r.c2{\'f3P*3N 2b/R.] {f86B&y*~֫j4='g⸄3se*@Lk \ hPGqx1AW..`\ソ덱{|5Ii=,G`FdU'J85]Ad\7zۧΜKҬU)lŊ'(D<㡣xR I$!$Yqlb4I{2=9mMN1I!DgQ0 ʠ::.)yDFIe2,9;[zufat뷔/q9F X"c#Bh0LӼZݽٹŧ̄kc! s6GzXbA*f੊/3' L AR 7޸38ϻ!NW.."ӵ!cH ]/s`s] GvxyR0_ð;XLk<΅bzwPa=X{~}=ڭQ \ OH,9 I^7fzຽd0a+˲r~ZY%J46V'Q YsDɸӶsrZF#‰Fcnrjnfkk,>|}ct). ;d])SL.w@*O\9ǑEa윛Z|~Y fvnsԩ/1ȥ޹rD\*i~vŏL!~^0>"0F! ";hюdpXifQƇV,sŏ\H 9dH$ϤLI@`1!*x7?񲾦33iez<[jעw RZU*e}XLgY1!I!0 27(8A s!1 Zm80NS}_YCK֜ Izktt eᇀ D`[Oh"s* B_9u~ll rĂE(uc" cV+,Jѩfis=Ú!1.MGj=I4(6 d:ި_QKkܺsk`0F "\#8&g`kC.Xfj?Yt瞽RVG0,u<ׅ3"}OkMl1ZJÿ^O*ۭZ!dh,V %c=! LG7_xɓjwwsL5*^Yu\篽jٜ /N~i::55>osWNM LLU PyW:YHtqKHE}O:͍dhYB?|? OE]4 d _J ,v#⃡iz;FfjBK@1CNzJdZ3^@tdmx}]åC;kFZs pHy@(Dq"bnl7M(%;Jeaa65Y+y2ΕaVz{kk-.-Y7KHicdJQ9Yk,z95*0<׎,˄%IBtEc,M~ <˲I!3)I6M3klT.;R\z~Ã[oY~~ wC";i?ϐ )K7}=@Lh cc3SS(N#z}Ν5srdΞ=$Ãv`0_8=Tq%Z)75kLDRrYJ*@@&&BpeY9g1 rXdnލ,Sq)7_R/Euv!2,qP|E M2̔Ÿkkf3SgwWͱX.BAg~\Opq09s:G Ba<N2P`9tT鰇LZBƹIMNv%KQ7:adYч\Wm`2DAq+!1f{{;A2wvwCm$y*R8a+ i"T5aT:< sVo|ًӟۇG(^K^Wcc)CV0N 3q^*Fx3 [Kcϯ@2| #Bdcq$SI!()?ٯ{g_KFyz93WmLd M/8`9^&Þ)9]DB:w{{{[B mYk6Fh˲,YE1QAq<&T:Bi5 #|ιF)E&{ۭA_)?lno{UN%_kKvI'P4IhpGcrK r3nWrPQ9p LMWyZsss/gZ}l0? W._o4)J:mooqƚR x"73=P vcwtBgsKTLU}"6O9!X zN1( ~g?{ y睹9)幹<vO:u֭t}W8 G*D.ϵ+Pzr3,W 7lP"X69rDPT/oqoZ*YoG;^˂*R |[8k(H~rhL鴦gWN2Z0&g8m>:>Q+,6Py\y! p#p)dAA*Fou5'1˸gcS7`c& u:EƏ#0d Hj*wۦV /ÅJ[s\!8&Bbԋs4+1@qD l ky*S*k(%9B )sH*˹1i>qeYG"U-b(Ip||Z J,O37|qo~R= K(9Od][x"9gTV+v̗0`u .tHFAL^z-$I&&ϝ=s5Ycr {-SΟ7&?88H~8wjuZQTO wq;KY:3t%ys:/uI ɕY80,IxJn%l_pnܸ9ZY&k^ݶp3g.,.&I(.]vWypowqQ~Ee8\[Ibz> CBdI$# `{gkeiU81v([cKR(*rZ~saGQ  L&ף./u:kniW*FZdž5Xւ }/ (BHTQ  4K2X<߫4\t:zM ͵ݣF}vn矉K~Q< t,ٰpO爅&C/Mh]p嗞Mt#UCzr1ȰǃcD2@r0HVK `0rr,Q)@g'2񱱉 Mx0{xSO-Nz,p9!`$C9|go*pЯ7B ;۳39\~8kKAtxx 0J$J/=#yAW9bŏ{i0ZZ>}Իo_ YUۻ;w`Q^p.cmt\%~UKWʱL߿\ĽD7\]:[yrzjbr̹CtUxp(ʼnGptV™ɅW,@@atO./od0.?aw9>V<^^v.%2'm(/pғMk$"gSqN)D c.2G} +N8![)KŜT`I iZ0@]}hlz\+a·:VLc4<'G:*}xs-I&WkNJ)@p8Vkc1X5q<78wR)AeYa9j)'kM6a6_κ~#b`{?/ҳN4nȈZW`Wbq9y[KBy;;Kr"p g9p(\n+0?O)K.oۻ{κ/n{ʕ_xojRׂ0o6R:kjsj%<9=;jw_f(Niw%zu,<ϗ %i:ri2AX8htww9Z]^%Ij7^~ׯ-÷~'a|rVѻgϞ7`Ꞟ[̀b1,X}{Q|O-NO\y0p~L /fꬱ@#CbGmk]J)\zv[$Y, vcF;{|x۫]Ь6J?=?omBrv67%FtS\zZQݗB(< 1رu62"R*Z)W1`1Z8\dYt0RR~^ty013zuY&(UCd <:ϋa`uD 9ݱm]}A8py0jZ-9$ӹ>rݭAK[[[7oވ4My ЬW9VͳQVW/,sδKިlֺOHN:ot]Qk͖޻tsgK}18*" >_,ɲLxѨJQ 0~4I1Fvhjr9^U J'̒( (o6s㓓'IټplOLN^:ǻ{/]+N+ff%rY[JGc+DιrsN/=ڽV{0|hPEzT*u߽f饽@eRc8kqSщ5o  SFe ./8kugz,%ip( s Ixl^8YDJ~w0H{ƚ/o<S#f^HӐ,-$,:u~s2ܷr10!tsݗ瑺x2|9gNG`ia?eD8R%Jn N9JQ:BJ%¹zGp0Jr677v4@ƒAf"wtԀawfy\sJBO?T\s:rE>NF&bʓ=wܩ3[9u&Mݽgϒ3{;i::{K`?O^{~~vV &h »-tT%64h.MR^%; Sz·q ?2KDC[loo޻5징s΍͗NR }c3}Ѵ>C> qO~RqǫJ|x=OJqp)FY9q~N,Nc4]"X\0I*tw8Ծ~v蓻N'Ϗc{Ssgce fB:m4J4QJq'߯7:;*Uj=E|njswHo4WA?6mmͪձƬuЙ<#rDke;ەrZ-KD;Re1DFrAs|gg*~p0H]8=tF06杷ow\ϝ?9`U?x?3P~r堔d~O @Ig\Cdh6&vv?xW^y9 ggSB*5 ]lllyigϮ(GnߙG׮]dzgf~ι fݿ9mݻrj.J^~܋/`Td:Fp0t?>]óG(ٹ{>k4KKZ8]@cGp#CD6t 99Ƹ$ǭo{S%IV.(TeentƁ# ,>}tzK=@<?tqtAdblzv2a{]~R)l'R@UDXwHIhy)+ЋSM!Ig:_f%ɍ;w=x͐fZl;?Ap>{YIfV&Rnkk|ye2DGN"kݱ~u!!>x ?&'dC=,?$WLM |xbڈ$Gv\8BOm NkpަsnoЗT2 g3xT'ơBB@șq:+ 9PHxO$|b}A/ѓtص!Y01.<[؉昔+M4fD4ҔJU GִAfGB"dyFd 2B34MQ*(98s5mahm4!"<6YkR`l6TYeY'&(gWG}!ڭwnܾfd).e/Ϥ\knMMwG!`ƸF0DP 5Nz4zqUkϞ<ڤ 3K3Yyܫ qgY6917J333a繵LjH*Pݛ^3cGAz0W*5к،1\h#n޸exV匷ZjSIO %(rDZODh jI@@KDhD r$i :srQRi$k=)3<1Ȳ1Ylk/8OFaݸVfgѠqo2"ORjuv7_|ṹx~ѨX; v%KQfYƑ ! utݾRr2rD f3 1D4sY1.N?s c{~otB"BpW"HEܬUƶ6w1Թ~1Ӝ;eC .L-A$"笅tdar͛79{.LO? PR6yd%P2V&}cA w6ʥ\_ʔ$O/=X?lwZ>ropkoW O0ǹrq.F&K޼;eYFjy><0I4ј,K`s(!9L7~r-R֛Ν?:?^x^~>M. r2=Q_]Oރz07!+4 b yuElL02r vv;޿ts?tG=DccG?hC?W|)'6# 8c,Wߺ~sܩ3qI"3(c>U[>H7 p`TJȀ#!x|o¿{W:L<`wwۥ)W.+Ѱۚ e-g 6&ɭ[\8Bqr$IHy )=S;88p !1ǜfD&t32G#DVI2DmteBJ" j:G!c� Ӏr"ܼ}t¤d d snlXg mRcN‹nHΤn+Ν;kkjwv-|g*^;]kTN\.5u!e/JiQa$s{kr86֌0.PJ`~aѨwէ(wq(G~m!Ȁ7չiv)p$"hk WX9J腹ߓa;gu\#c ^޿3;73Pqy~pppe1c3+++B,ˎ::xW/^ۿ}SSӓ(7/l8Lo߹ Lw{=<ڪVJ͛7Zdf! NA"#3JFCw3.\ A 89k LH:;[R /^ZZ=3 "#c?d5)̴d å !:zȁY: CÃִq:jسVf&f2B$`H_H|$N522o vgKsWO(0^@D)E1=6sL]s!B"RnmmMNΚ~'orVOiŹRg֚P`v&pfsݻx㝩>918`2=im@ygYEa(udd:`mZ]ps?Lln>Rix3c3%LR ?џxꬿ}߹QPJc d1Md%pA8 'yΩNY ܽuQZ)n4xů$1B\z$[_yApdwd.,L//6hsy^(OON#2d9R@pt 2} ]xch>NDMF/Qk~7禗O/=X=>SeD~;#Εqkkz`HQ8u,@9'rؑr΄18+tk<QpQ 29.ZyR)sQNIJ3Gj}p`9̴ΕID"#9Bf- rs^p94> =pD:̕g{vN+ñfinKHP"ZZ\-b!D?ZcQKDcccR Ņ`ժU!1pJȅӹ><<@I3SSvvvFX}brrr_*M x~^[\wN[,CF:u\@E8FDj?2rS͉,OuEQX:k̙JziZ)fF>gƀ1a wwǛcc GeYW_{Ks3?_h$j }t&y%.UAuNmuDamMMa0=33;9$|S*y:`8zll8D@^EXcs(dbZhjo }ez73 F{woV,l]xR15))A䜀Jc<<Vۛw6wzs_;+ LX@ 9"D_G&k^AVL"[X==[G&B0sΊE'lV"rez~LOӟ?̳ +W2-TˀΙ>33Us0(Lfw Jei{WvvqxxtڃpPk4+M{Ӳh̋O;8[4Zg Fi=f@d`ؾ{}\Ѡ @PF+ƜK#K^~(76woܼ6 kONM,-7 ux(xvf>vwŅ t֥YNb:8c'XEHxRUk󧟾z)?fnjXbgq1ZGp};FyyS+>2NΤtYƤ c?xDrVFAspL) 7"/|uO1g3v'[!JBJG 255E\\Zc?dvTAmm (ADB<1J@YT*P24I2:wEQ,\8gƚp$?}+v+i}BI6t|9,  YL0$_z xfP."R1DV9nnQ811{q)HaA\jQ?B/qs&.EF\.iQVqd+%Wj"BƐTPXTyԴRYf#̵g5)Adcā}h- K@3p8:w\})wptx̙wn_TG^0wߙ_\(f^սvTQ#rr9\Z)ut4IZUH̳O5G:Ϭ ng42nowW0GIG0;0Mt=L\p13QB5&&f/Ͽ77jw 8<tp770?@i=RUfggͭŅJgwZd!p 8 @bCd%qunccC'\H)\: Rnsppx:YZZvEǙJTX:^e3gʥs/<8Vsoww{\N]TVVzRiJI1oʥ1 G9`ht nsSg 5^o4wn;::"rhhkf܀L bU&k\/]y[7=160T K>Q:hfz^+;؏$=<c&n۽tZ@L̐IM:XQpLdcoxq?xM Xuwݸ?.?og_zLd;ϭMX$@'W8/V/ KNkcsLzȀSַ6d[NJd2[_#pO>dbG w=GOG.X\-u<=zD8CKfLrD+gu23h3uA,YGơZpƍ5^O)R RɢC81dHDYdAoE Du1Ty^ZェI:$SJJnQMOMm<0ga6|/ Ҫ#v7}srbjO43 ZD/%Ts ?::pȀq><:_#R@[#RR g1]D659SV"2!\^r3&S@$cPDP0dsTT68WAJLD`9gioAԂcϕʍU^y$Dѝ;SGV#(\ixa"4K4d\jf"Ktqii~q1#Ν $5v |ꩧ1`9Gc2dZ1Ƭ8ؠ߯Uϟ-c! HQ냃cbkk;I4=ffRnd|J)WH:U)J!Jx g/iav}8vFV ( =ZT)WZw(/gξuϟpksD_tǽ86f`$ XH;΄<;Aw-!/Dsqirę>c:'<'X'uR`o=5=}̹ ^GH*"3#?|}1"{V^xyr|0Kϔ1 GF;G/ eɌ 7@yy8paqye\L0'$7:=L!˕r?Fg/߽{QoazvnQ(ad+DJGq/]K3ZI4Qfo*g㓥)\(%N.Q!CH޹{^׶kz|Sg''+Lo9gŜͣIN~Ov;-خ'J(>5GS)wm Ї}[/nd ,9po9L8(b Zl<\7T.0Μ~?K Z0A׳6jND4<+`0(ZJ) -Q )#BHF|G |42t3CYDfXEAd,Q$ľH/~MOϤJ0v7gϜ=[&&&Γ_yQ^{{{495 Mxc㓈^-w;ͦRr4J޽$sRZkR "8k=N|D Fx@8\0V 4-.Rx٬UL+QV*J{\T?l&&'yi@ḧ́GQGy?|ƩS>&+W~n߹λoG?dyO?so36|arX~Q F4LZ=}r)*\1у"(<1Z[m!/Xsv3|cs"hg)qB`?$} ՖK%[{Bx{{QXʄo}J\Ra,, 6< 'b| 3n~f<\1 D k[Gw>PB՚f)ɢM9X,#G=kM~NDOQQIF% qil0f4Fkj>mιK/>EDnl; s͍V8^!~f1R˕Ӌcu!փ,M|aBo,pY&BoM  @z1@N8n~cy^^Zi4z|#", ?-O'>59hI"J Ygyno\{;QT%ǧ'GiVqv 1Fx,?L`+6`rkUd(80Vأ#} ?eu&=C8J9BO-_gqdI ("wQUS`X w:[xx,YYY]==0O2nyhMrG ?1NΔ=*:ބa=2/:ӛQ;Ij*  GA!Ly'@A$":3I5 瞼DDBjI% \eft8IJv"^Jۃ_wn4O/K%=;ZLQQoԜ8kŒHX0@ŹΨ{]+UaXs|\iZ_x?uy9s{kn+Z9eZ]7?8+[ϟ\ikwo]xbVˊ kieQJAPHk RA"|8()\Cki8Ho^RJ߻~ ֱ':N f8UJ$OFHc8Tug>~N $|"eoQ2i'ܻqJLAx:<r\ݝ|gq1)-lt",`f9p YguZkc, +,vvvIٹ(X䩓޻JɅ*ժfV-+)keU\62\ Ihb=3xzP rj(75Xݽs/ 9sm?TV;ޫIs3PZ/cdƞ-MRKq'www{~rc4\V,eM7_]"g͇&XI짠ρw~<Sx5tu K!]:RT&Gq^kK=ƽ2d\Ň0("$c2b!Rey )DGIafw9Yc@*+8'T2HI֙Όq޹{_w?Ο\9JQT*%rc2rߋB;!yAH 쭅"_A )٩aaX*El$ C!u.IX*HlD)sJ@BcRRH5M=( ύR1J 4Ě1J&cfU+ѩ;V0¨zf'I<_)WL>l/ uN?i A3R{/vw>s+m#lκd;@9)Ld4wenWo Q% *]t&a)BD:@dT*UVNZ{f$9'%$B wg+Bh8>odq| +ZS RkgW!"8F9q,&Xmuoݺ[X8q[‹Yj\LֱG~GX $ @X ŀ~Ԝw Da )uܔVfN0@++-i&t{› Bk}XE#` ^/2z)Zr⹹N}vZ5}Zj@Sv͞*Q"H0?ƭ[wB Y_yR'KRb2 *eHGr2>  <5 B%A 6ֱg?3S_=}banAdY(DbA(1X'"%U=9/`ݭI\ˠ$3M0\]{͙NøqR{s֨X}T6ܾ B8M'f[];ښ<W8F` ,m$"ۅ^oшwvMB%٧D Lpתj%L,ժ\\j\w`v֨0+RV Mz[s3g.&ᵏ?ZZ\n4?@XZtnϴ@38DTRN --Zy[I8DPܚԍ,˽WI2~>7@*pT[l3!D) Q6>U}s'O֘B9 O Kݵ˽{kA~p Վg!x2dD Dz;(Lgj#>\y+$pQ QI"X‡(IwA{! M/6-mb(#ZO>'+'ZXʹ+zp1Cu~8 ㅯ[g&H@R~^|) V*ABX. rA@@‡DwmEna&`BO X4 GKx9<x>a*zE 0\؞vFG%\i?$*q%j,D9kNgv̕dؼQM $ P"y'jHXc~f|ΤV?zuB u gH  : qe(̲"yİc,+(/ _J˅T5kR1$$"ZZae,ZJ)1Ms!C=I(*} RR! R =9ZJv[H3K" #1p UX(+3Íjݒr9z嫕j<  ZMc¢Tݽݻ RXu!wI)H=2~o?KLwg>Rؙr%eZe0Z aɍw_/W7n\LgϞGH!Db$uV'K$dY:7yKX IAaʹBݨTʳ&OwwF~l5y?ujTs:vcFqI ;ͺo;U@y;K)^ RݺysssQk,\i7+F 4O# D((,?~vN:|nҥZ"VC@3#3?ClW4&g"?yNw?`Ņ/\h"!l];0sǕ={/h)LPg@qwm8HvvJ^oMٿ1_KY~ffl/#i/ތuN }?jH@JdL;Rt&Oݻ;QhkrV/1dJw UJ>9CFvIB*wv'~woדDJQ./[Xk猝C\#D@TXRDA,Àqy|17cw%2{ Z($܎no=43Yj ~)dnrc-H-Tyc. #)3XgP$(N_V>%"*2/xM֍{AKkktgI ׾P0H!sqϸz6 bfIku|k ƩK2J D!i- ZBD aBX/_~zV>lc^#pQ"x9߳C~BD=Ǥ < d޻Uu8OFW_|A)_+"rցD#!" NA?뽰굸RW\Ёȓ$TnuzD)$ O(pD#I0|>jȀ>%_e?D35Ɂ di*$ֱm$twWs M`ڷ B ZÈP9wF AB =xB$IPzs !q읷lo [ʩ/XN-/0SVIK[x2m!EV\kVJ|BJ$H5JOJ2z$rΓ@B@JaRaTxfV`z$O5< {.ܑH8؃5YQ 7Ie9QIneOB`;RX :>3c˳x&D䝟$ ,K6IC%w߻S0:Jw?#ʰT֗HHm,{cғ$-g5ނEO ) (?p[{{gάj0o㬅wu{{Tq':BކA9!:A2w6MLwo/dzdQı>' rj,Mvʩz°$$f֊_R5716rJ޼UfvκѠiXF:I%(T* J䲜B#ՁqZtm"` `DtfO<8 K# >"@y(01Oqv>½isy5 H7&$ I $*1+tfK <ǀFOosϏA?i]Ӳs8Ц= :(p*XZ AFHà6 &$: L>V$0`̕JrkGR*!Iy%!p4r\acDRIƍ[7n"zZKr\z嗂P|"w9k"xדּ1ҁgHZX_T̢3d`򉦭D3 Eu3qC"hg&D$$$ol3YJMaX%QT(Ǔ$( Y+%MujsFo$!)`-9{W@HYW`L`zx@H(wT1@RĈYA"*4%,0 ?xٙ\TV%uo/ۿ?v/|eD'HWʑH\M '(ݝvwJZ\U i=7d<ҽ;m]yv=pkfd%*)u$٩ת]n\?1TV כe]H^a:L0t$z3 pjbs`PR1,*HU<'$$%Z ݍYs'?tsRYFJ{XCcՏiqj#,=ҪloǙ3c RX白gKDj)}8˕8 KrZsޖK.DQH3ij-IP2Iݻ;?7[ I@8`D+R22Ks(`HC%Uf7aAifQfVN<&Q5YGZ $'$!PR@)AG0y|j>x>< "1LA`[2 J(*Gyo/5mgƙdH rK( H)2ƥyp854J  S6$w85, {ffg R ~Xmʎ R*2Y*U$ffk][ܽgm~XTx_(U yiAr!=a$f`!Y D{pee<˲k׮ǓNg6ݭwu#4$KQ,>/ Xޣ c`{=!|G'0 ~%U%]%J``I DJAHT\*T@R$p|vDh9$>l|Gk8={ &(ֹR)a0G,; ]\a;UO葿A>n,HDQ%4J9$DRy{HAKqg8HW+,5M`0h[Zѫ\qzݰƵk3s8}pԯL,ܸ1?{nxDBܾ}#+\XZ{޿zj^˿K$[@:UZ֗ރSNFrNaārQs>CV ޻ @F^ B268kPp?C(]p8{>xRT(dv1ufB7C2jI`" ǦS:_H07.#`ʳĤif{'^zu/'Z$D@-HyQf|a_}{oX 2A[{X `ccͻru4Jzu>BBORB u0j7Zuf]W͒l\/;窵3)w=xo KG& |>mDm~'zkg{occG/¥KPJY>H$%A P` HB <(@Q}JGN߻yl_ҧ+ OnO?s'|Nޣ?GJuz?Fi ZH9.,؜\m^^mEwThn4R<O& r{̓@{0Y!#{ceSi}>ǂch2@Eq'A ugʝ+,ke2Ṅ0&&fS2`` k>^0'/BȢ؆A\ DT1"  &I߾7&sKzJH`T*qtÊ ;kS"ѓO]MR (JteHOĂE/{&&9&c HTeaG׮]@sBK)dɾPIy/٬^}EUB;%̹_}r\mwj-/rA&IRZh+§uVJBP+|6 G3w^HtO:wݻw֐EKZW7I1({# m !#"do 5f(s˫gNf"%x3.WJ~3031ރs (°$[ݭͭN^yW*xcʁݱ/ݫƑh|,\cDFJ ػ$;O?;;je,wk{;X?l~67o=v;ǡV (ChJ$GONWȅ|%F(f)5$׮߼qY6yeiGsss/t1S} ]3Ρ B((h^PSg [X2Tǭ=)ԣ#ǃ`@_xx=)$B+?xHJ5˿: `uğsxVG2dj;ynݻroKc?_}DA3(~Uy? @%qY@<[cTL1U 1Mp4vUk~M^5FA騰$e$`|vx<R H 3H)I`Ey)AYfsW CcR$M(بW[IrpP=?dYz8kolnݺulnXZzصu)\&֤Yk:scKo׆(5xkg\E}Cc هAAAsF֖^yo߹ PjQf(&A*[[[ׯݸ兹,=sf)},;$|ݙ$gv$@HQ$^R|X*yg e% } <؀4X&6wZiowRFS3`?.1B|L78A[RǥSWkF1kAxeB ^Q Y jq7 ^ngϮ?$֫܌A7R~Ǐ΢b(Ta3:d>x#˹?An9*s(͟(IWpv2R#U\G U#=2{affZ4MzE2sΆk]&TͳR{z{A(Ϝ^1ݕX fv5Ӟmn\>C>в33;Ioq|{\Đ$8}PVN>-d{Pgffkڑ#U@P 6yB`b{l7@L[ 9~ hIxf/UaNzkz AyBz M~V; "י+Y:1C£DM`,dDj;RrϞ@ou?v;򉗯^6f27\.Hh-J7~nVlnF OYz= /l$t\Z\XTwk^pO~{I 3@hh6R]@FU,),֞c=?Ç')@>s7~g~wOR͝9wvfY8 g  g@)BPyx|<gr#_0p LxND|dr=x}ms2j)3f4{6.&"TTR̃T4qLONQՔRD䬛d\Vux4=th4:yr٬J & 0S{w6;weYzRY,,w-Ԁ6^z(.1@nM\*YvxH!um;B9BTYkHPT7v6o ZpSgF]A>C $$%8 nOP}TK33uqii!s@X3P$xP Qɯ(3PFr^!Ke&A{rI΋03F\][JX[տw[L+Vdlh2B@Kgr BSG5Ӫ(mUa?Z[cwALYdwΩPfaCoYMVku}:@;=n͑ibGmZ>O}gZ 3sTʲߛ͵OzV_¼&T.<Y) D ʭuc2D#)Kκd<)ժ2'^Z_6ֺ;APf>T4jZ&r@ gJI2w.ufͻww(+H)ƖK=_h/\С'{('Ïӟ\iݝͽݏ3 T NUV@)0y/~Viy֐Rg0 \?!?@ 1,AP n\쓏o=gd25|xbJZT̮w^*]"<ܲ;v { Oܸ~' 8k-X^{4*E|6W/#5HyZx~FٓW.,@ rA-`KGL>7[llyX @0T2dO7n?Ox4WKs3$ h(%˭jR0&U{E]$O_txb /^+T+9t=0"L;=!z##jx> 󴉟'COß" 1#_e>4jLyph;P4R5[@\gRRO&P6Q.76vI(ꂊSXHB3zǹLa^?o_ 3fYΚ4ovܨ]|~2^H͓@Jy*$`$iߟlovKQyyyժ7=bk2I>L o~37;ߨ7a^]R>fquM&ou'],-M+6S߿qY;?HdV*3lw go;3fiUTk 'aRjbj, i @HG0B:%EB>|GZ>ڭ[ϬkkNv:. >xD!m_3p^?JP@Kr߿aww$+gϭD Lk,L;"<T1S î(V yT(n2=aN&Va;[ql2f& #)H $fpK-10MS${in7Z)yXJ*"ynQJAQ BaowpmcR) `f<ʥ0`RՄPinJ=9!eZ0 SY>$]|i}z/~[Y,"~?Bj5.J))UH IH?|dVfe`"ޘ`orZrLý(Lp<JU!5iߦ.ˋ8lDh-݉`+IjsWVoݼtLqy&i*,U*yxT:)*D}A'v޹er+$kV[h|rQJ3ƒg<cw-Am>>l;Aނ!޸~g{kw;K;qYpD>VBY>KNd OS:|Hrtoo%v4u}`+W."ECQO(X X <`X/b"/!/XkPHaB /~_N`aq?M䲋g+PJTWky*y@vHE+LIePP7o\Un\7fM+'~g&\Z3'};GW^z__y课t{fpHx/@jUc,\QE%#x@" JI P c0&OmY;73|JHEy)\{&nH(՚ĦZm#0,ocv&YaB3=I9gG!sgYn k\Z$7*Z9 xGD#;D(31Q>v9nwڵZ^i*V&ZFZi6kU@D:ϝzܹx{ yrC !`<GE)0KP>H 0͍pQGZ fV…XM;M ܋V34i>@ՖRf劌Kzc0xL;3x~PCD*X,Eݻw[g[[&ħϴZStv,}z)}v'#7+E 'Z.^^ \Cဝg'@'717KPQ" K~;cuX-da3V]Ie֤B >YʳF9#v* Ù!ɭx9|ݛפgW^}/w ^gR$ {j|buՍ%PىD7O7`C0 F dTDtAq* DD@>|.|xβB= "67){$RL$s1O( YCf{;zIcR)2y= bkaQPњ%tDXchONxzx4By0r,m2I7ַ èR*+R4:FۭV⋗]LF)\rnWj?Kͭx8rrIJKW=8q BB΃l!W/]r~{Va~˿WWm6 d$(ő&ھKps/_jW\)Z0GNyg "i+ `wFd7RgϞY۸u\k&`{9w" {||jmvn&R|]axY: UO򷾽H@>q[h[_^KY= o[\&@bov }6頺#w}ZJ^=8\Zh<F״vwAI# J `0̲E{?I&kkD=qbifV*RHIHyhcAسRJo'Y'oϟ=s\Y8Vˡnݽ{/]{{J$h-/ N?y7yml8)E)gzBnb!y"H!RJf = O&)R FvJws󵗮٩ݍ{߻}ŗﭯfR PjRODHZa;/ E{. @?g qHn|zx'1"δRL~ {3 :R"xa:(UǢ|9^{_#p/ouv!yxZKGv? Gp($R |'捷GCRr~D:Ǒ:Ԩ*$^ 5%cX*pNܽf[[ۻskطFK\v?JyiOBgҥp5?ڏ~ J8/$PCpi&b.A.B*ĄU&M(! q *Hb* ?>Rq*$sL|< ,xt?y<psrp~JcRX%*,Rcn:3R_J4{{}Lj`PE0GMwg㢨0p xs E$Mv qfYDAD Xqe<ꮯ4g AcgIfVݹf%;k帲0 noW+ݫ?wk2ƺ))&Dl;x"i> c'AH(>P[ٕM,1$>x{eeSDfN.TAd~O3ͥ`ov굚qՋ.h1yE tkcP XC%c^…N=_{j\V H +Ia9 C`8wt{n(*9}^Z 23 s~0:(§v3unN UJf쬖3eΐ,C*6~8 ޾|b)N O/胍ks3͹B(>I]>VAEqi^{gKA\ A~NŰAu9ʤ +Dֽ~8*wfssMid bEDO}(%XN%4Offϯv:mN@ŕe6" 8j(}Q,SŇU{62smw>:sfԩ%;??s|H`  rl@;wB$?tK%rT5YB M#F_X R` }N|2M-= "&be5!/~#mas|G>$dR2Sf&A  !<:h3Z'HZ#]ZG(V[wvny+ ' BJ)2kx4ɲ w[VqjsrL{d&?r"K^oL;;( IYbQ  Lb)i4Jiojv8T_{Zu/yjeW_~w$N\+~i$.^l۟}Ϟ}RufN,]Q$d\O.OvF-T%Tqn s ~%r^:_,8GRIr ZCj@h_I?Z @{AdBmX.u}@]Z\YY,ɕ\CNe憙=?+/}s 1rG>|Rwq|嗄A'fM`/垾u.IbG bPko^?&o4$ŗ^V"B]nijSJ,$cS9¡y=JGM1@v۟|z0{wkR$ KN'n5+>7:RD%u$"vx28f2;y)-|~̄ůG>F""tT-;x6ωO{ 1!w|"oVq}\ ~?ȍ:M|$Lܳc{Rz1 ;!y{t!!L1Zm"VKWsBAdr4{=k<q3A(\ig70 p{d? Xٹ̳33 4M6n߮뫋'wwvom6Z{7Ξ?=w7[ٹG 3* kFөpw..\'?$p__<[o}/khnvhnonJД׶׷7nJ\:}R%iWXՉIZ+WM`rf0T@J@nvC<3|zȒ@6H^0 4^w0JQP^xl` gSJ@"'TcG'')5g,@yoFYJY-l]yu Ngvurchz$@d)A> ;"7# ?x.@ @py@AcJPlwYi5(6qδO'Kk{ )KJqiroGl2ƾraʙv(ŕ^\\@j$R6#ӊB8+(gk3` &"2 BϬRg>v&B7vc" A$g0H'oOvkvg?~E^om]'>jz;>ZH2xzoF{[؎pby(7[ٹVr?Q\;>zD="Ef|dq~(ً󥀤Fpx+<~Db9ægF (rg7oUwQI?[@=W"4N8+7־|M /`",hyOHXra!k$)!G`q.wѼ?#1eMA =q$2lMPa`)6d^%g g*x4IvɵstN|~tlgIdkL9ֈh󉷮joK$X)|?ifY.IjVGL_}q4It$Zz):h0EyHaEN](go\^ &}=v*T3 ,CD!RkPh- %ɾQ0|Ar u:'>ɻw֝Z N^NjE6!·cPw$ 7oFQ^z||!sv2x8}#7)w6AԽ޸ި]k+/,̀r 'A~XO'?ܛtRRtmIbn{~A=lfkǧOՂMfRl-?p}j7PA;X@ȅ6B@!Lzg=3<羟y|<X)$g6E̎YfKX0| Rhhժ,lgՎ^).Ŵw(ܹ&SKJ뽽$+'$QEfMH&0RZS5J*]oOvwt\T!KGAZ {ㅅtqq)gܾl@x'cdo R]|0.c1A As&4-ת ?CCY:n  y}gwz{Oz< qC43 M%7N7Aϝaj %%BCUPF:T`ccәͽpRc)ݬ^|V-Ͻ[\'iIw842 ]Yw67 *q9!_ՅZ&3.`% "3# ~D@ABJ8ϸ@a!D@g9g˥vW-)$CP8¡lˡ5uV^k3pW *{f "ւ*wO_8Ґ!@pQGG9C̃Q s~'%in|u9}C T9^(06^! *&Qs\ H)DUJF8aT/ Q%uY,-^wR f A.(+0IdhGgǔ< :#ohy<쭷>xt'[$4 +[ Ví6{ӨPN^7oΥ>m͕D=4+.Q)aGC! saOC$@+4% NGַke{|Z Isjtm4mu L#ٟ~6݉T[f5yM3h*5AMue2V.}41RWzRUQ%"pL$ !pMeQ%"I]|̞$ Z~_u!/ixPG9ad Q*EԠM*6\/AI'򐶗XO;|w ?߽qs9;@/:09RiUc(I찴K':P>|D'}vOvg砕?X^w޹A9.AħJcG':DZkܥ-@%dH~Ϋ=wnwyזo;ґ\~>$??y}sX ` Q|3ÿUK~ mb*, *HQI$~xxrWr5Ҭ97Dekc8ܽt8lzxzC]ttS؟4:h8T~49_?9+v)?3c[R7>M6H) uZ@<,RL:ZHw*:f*:O33"xu^=V7߯ZO|z6B^K ڰ(?*|Pg3 uӼ߃?;;[7o߾{ocog}} IM xTIv%tMAQHD]YpiiͿ?/~I[o޻GoP!M‡Q=A7ՂY!rbRK|wۯK m7߸sc5%t0ËZv;N{< fҨ#%tSC4nXӨB"QGFKg$4;\}Sdia!f"ҁQgFaJx Aʙ)IHr4H9}DխKe:qX+D"`o?ov7o>~rȈ=#P4CJ RW%,$fNRۇ%޹JfY`c֍  fH3u/flnhsK?v8|7?/ҴWrӓ<~{ki9T;P]|n} r7~nY_S^[{om]x6a".9t{Ox`S2չ#*B0UOsvFja4k1}==a+YT#"Aϸ109X(0ػ@g܈[:usi@A@l?7f٥ir?_^nA8'@A%{iO0"EQL  nnz=Ȟ=&m.[ʁ%af?&KV0{!?_vݝ[|뵍[kKI2ԭa Wd'?E^Zoܻ{j=%~&AYoQQ̸.Y"3yhkEbDe00D351sMk[HMq-TR pBaQ0:ϳ|`K'*mzHS"dtd"Ilo}w?͗oO~7S ]N2<Zz в 8oV{~ z{~[vC诮Eo<@䀰Wvp3™; *"g[AO'G߽<~OV; YC8:Ǫ!\<pXi7|8Y}>;O޽KSmv>a npBw ŭ'q URF*rqwbjH%C Ø5z| Te% <9a$TEJ yA T:cJttWsq%N,>տw/^mmw:o=~G{Mn&1PRJM^KV+ʧ|o>U} nu^gshBTn23|IV_坍[Վg%t5˜s#@dR'/+ۼsG5,.{) A0?ѨIPY/QXȧG-$00 xfN-'7 K1\jTRzNQ2P9|qF֩)Cz϶wZiW[ޣG؅NɓKRRHӹD.2*Hᒥ|OoϿeY?I(I{?|M>dt4)TKY8F~[7\{[/XX\Xj> dA(u nW;2\'S&mP3R nq`W@y!i@"@@Dhj>'bw9||_j3G$ i%A\SIFukc&y)p=,k%"%'aeq]2<|p~A|/errZMWsrnTndJOmv??n?ɏzK) 3HȴCdG;QD'85;x\ JLq㊈6Vv]^wtDH U;YUF:=#+b_zS:=Xs.{.47-@N17mhOE'- С }^IY6Lt1ǃ3FT&A⡥K- m[ %G ˌvxt4.Ie+&K[U+MZ놥 x2|>>7]?ȸTN Z 9;u~ϻ|Ρ9L([ucͮU߫jd,BF[zŮlolW8?LΉwɍd1&z+`H9WXf爈T‰3*1#m!C9= "ac b P1zb$|_||w~{ؼ$W$eva28QFWvw%IʈJy1;>t$TA{+D-N"Du.ǽK![?id npPlj8FQj6yqY䑰3aq&"r$B^[ڀc2͙yŌW1F\$QGU-! ABpq-,&)2)W &&rDLU(Di3f{u<gϞ}G;_^ܾ}~y u$LJ1WiBMjfOv I` OrD}W_|%7o.ni2Q!GY!68 䒦i'h9Շ>ޑ*Sxʑib"&f"ct-x$yȑРq=b̎L'Aj ETɧBzᇿ^__ ZH9a-";)`eu.E1 YpGo{Ϸ_Ņv+,o;Yu[N8\^Rqj9Hyy$VP"$y>YX4g! $J\tkaa\ \;Ic bl`,EQ!f2p1 2X &Qѹ4}*/Dz;R[Z(a͓v )| H8@ I<$MhOA%@n}Fseg[1'S} %fB.UB>:_eY=&I\D?> ܊ϯ*-HFNc6Ǜot:[X //,**ISJZb '>mvCpΓw:CnH NV@BL@Pj' p+;)2vNJ;Ӌg=_X%aLIܦ e-=e JFyUa "YG-nUޑwD SB18i:Υ-R@8'p\ a((t 4. 3%4IHEDp9{@sB b(G]9FL ~`\Dvp0X yzE;!J4$.rN 2&L3db$$ "Wsf*"(6/B*aT@Pah4=I4 ׳A4TGe14STN,TJP730+ҾuUU8 P0 ø^@=мuf):ζ5heo֚#  ϰLJ]JqD)4.$x9Z.Ras;gP]DDDA1_+EI@(ʶЮ=YŅ@#2*F%@`8:$()+ BEśq#hg\B(-xyϳ>j6%ƅ1C(>T")s*@e`$wh6|45^Q}s2 :^w(?u13 &[W(`g{i_ݟMțk:7=>!X_|9O^ !:\2\Mys34 $8OI>-vhvKT|s 㱊Uu7V{Ae!e!+ U*F q]"x񞡘T#fkP& 6W(@u0 㼝aa s 0 0 0p L۴XSus+{ը/8rnT\_뼞}00 b 0 00 0 Jjtʍs˗'&ά=4lY7x\]i^Gs{Z-ټ &gAj/ SO_Vٽb*-/ux$T"閞SѱcN{&36jY3\5y'jIL:qn7ׯS&%Q9}{z44Ѱ ia bU5&>5nM$>ٌW,53mڴ4-ȸ q=r\_#|KSW D0uQL<@3ٴ GgkT2,Y(JA60 0 0aa9aqiXvaaaaaaaaVG|TB3sI g3SYji%4-i ۹q7e'NPFu̖"O_FE=3~fԏ>@1#;MgLE:z{3h3oڮtYgim~:Bt$Zg9>/4۝{\50 8LPJ#t9bںyÌ_gŨoU5SU~9U>,?9baaa`aa9aq*0 00 0 0 s 0 0 00 TyVsOuFfeϫ΍Ш=9޿art cjk9^-pٵI&Byga]蜯otݷ6Q’F+53BJ' /20iWF`Kŵp멓5v, =r5g&00 0 0aa9a=0 00 0 0 0 0 00i3o0ojg5\ۧa絾0 fUݿJ"I|y}勒 m*kfN}9hoX9h4_aƩ&zqƌa̩`aa s 0 0 8W,~0 0 0 0 00 0 0 :aq 8%,*)$0f߿_בs83Q;`̃rMNs`,.>bAjo$$Wv baXͼαaƥ{ti#i.oԸ5*i'Ѥ0 0 øN`aa|hmg-_k<$da gs+u/fހSG4aa\4fk=ϯ}Ozj`aaU0 0 00daa`aa9aaa0 JJКvhиW/hk4Y AF/9}0 Ճ6c(P10U`o\MRUsIgpZO3*ZS><7L]Oj=fGg5ԌU֫ϱ^ntKٞ*_zKkK>OP0 0 0 aaurTs,Əas8fa`i& 0 0 *8haawx|Y@ 2T}|M}\β|POzVy^7,O5_q}f ?/yߧ]\g4>:f]}6Ƌ4]ÚdsiʁS ҋfN:fcSQܪF+[tjwVkDhٲi\gP\KwsϤJzILsކQkRE傋6{˓}\~/c+S}lոWs+`6s'c:OJSTq ْ[)lhu;;jEsd2L_=aFQb1i4_9/0 le@sCǶ,q`wR*ص_HbkcK;(|7*s9ILz$ț= @7_>ƴIո1hs:a4NnxFe+T( E$&{Su⛍]1pIʙKScgنeu:SgN>>wG*U!:]kVe2u9L_= 2da~Wc =Ҥ]{Wڳa  1f<7`pȸnj$Xd5tgt6ung#JA'j50;C&e'NW u܀pc\jjCG֯W|C8sX^A9AK'; 0 ?،_IENDB`cyclopts-3.9.0/assets/logo_512w.png000066400000000000000000001336611475451620500171340ustar00rootroot00000000000000PNG  IHDRt4gAMA a cHRMz&u0`:pQ< pHYs  iTXtXML:com.adobe.xmp 72 72 1 1751 1 447 Q2IDATx]|Sؘ 0dclcƆ 6]-B]K6uw;ŭyަO~/4MsCDxa܆Pjsp3x0^z׈~*+OY?k~ʯ ]c 7hkF MB,ZMhxMCы~>>*rz7^; 䇛\X//z!I#mwϤG+|e~fWp9HOG7Oj ȓȯ )~k+й!hn0(L %^/ϩGK\{ % (0``8_5O\9O. x"I\?R9 =< ."KD V7ižnC)iXOR t~Dz3Jχy"R0p;y^_8`*kxx!LOypo_>hL=qÆ{A){B<1xכ  O攭<2zR(@+{x i_`=TğYxx*`8SjϑC nh +/^hxxE>W2L^>Yu^BEkEqPO9Cf^ޠ`S)L|Ex׿z #~)[#b9utxDxij{ܺhwz-xx'&o ދ3ɩ OKD/p(S+[QGsԴY {.io /b =/֐w7n2>I y78W,lS %^Km EDɦpN? F -+b$'qjzH ݊=Nџ^7Y=wU L?>b/7yǯj^<XN}m 4=L?HnSq8-CCkذp=pb/ĞI1e>-I|N"3({/ӊ:1ߏ}.{`#/zC u gxֳBQn}`PR1Tp3JR<#516(<@*"Hw;w cKsrݠJgnG8W,j&j+]eJlC/i7-_}^]1iJys<)\L󊩺4K5=֜URt-Ӟ 0$@D|mOI+6'ܳ^PU%`wUtXZ6$ |raH69]ok,/IM ںk(j^`$/}!^6=PbےGFpz ϞP1xכ`-OrU5Y,NU5Q[O4uzWW󸲸!'>5C7e8*ޭDVAJ}yʬLo'w7s*;ʹntY՚%14T`*ZS*jh/9t|0AB !H}vr2˽uT?q:n/ v߻u( 1&1W[VYiK- WoȢj}၁Wxx}OYZPv ˸ŏ3Zs Uk=簊:J!9ï 2S Lw Cnܵc-|\** 6?W֒(ON^+f^VqՉD5`g.^ҪSNx?>\E$FqL;;'NWLtnu݈WVFo̧W13ӃbKrXYu즪ڶGu?q!3"@=AֱʊBâ]/]c ١""0!nrqz/_6gLE#ABzGP&,~Xr޸v@6Q9lne .i"j 2 t}:ǥi9J*˹5%yʊ :w P-KL*UU^{&5!%BGOJͿS*~m%^H"+H#QDR !Iz݂sWΧ?llC<etA2N#'wDݯ o*dTgTDG&D&1Rrع%E;-7޽1%p;?l-̊ rrwLt2tPpECɲ0Cp떂@>0 ^ "a+F8Zd{6 ?"?> /wkZ=C|(MH 20sIBVQ,:HkPK~yESB)[Q]*.$Z'd*;S_QV$N*_ɫ`s >~U ])gs+yi#j'˞?+iaAn/"9EC5-KZՍ;-^ѐR M ,Lq-UF,aѝMsA$8D{^b獘%! v8.$/207L1"3 80)=7Yna,eamcju~HK^<"|(Mb)3}xM&ƄAwʼox6TW5*7;jI6cVy'.a3ox7[[hh.pmuJ>tzAxB$q7BRiKFV]%Aa#('jfRfY~٩EJx `PPũgw9 auJ&ÎOV&qLj^cֆQ@d&! 笤{#㚪R]yiQcKjW]Uc }oUA  Y/f#:Uz"(Kws5K0aixj*gۦzZyZ{[eX9ihj]rn+p}5@%B>w{;ӿoYam%*1ޣ!(CЅ:*ZϿQY 7~ԁ㜔T[ҥUNHv117 01 45d[GXZDZZ3}k:+Y(Ș]l$f䢕H(Jv+ A.IJ@s2/0 qTMHBmb~;+Yj9KXOtq:M F>fvW}JxF~2Lo 8wt=EhE"\'6/.SGm 7 *7X ֳJebfȏAb$y?$4F'ΙQRE!z ws[ kUݬJ߇/7r;|/«wSY !.]%)M9^[%ϻ\Ld&HN**iM箝~;3dmvC1eYKo%#qR%5wp3H pS#WeurguIKK٨˹+]"-xwbc| 9Av:~emk㠤ln&'.%'?jJg[ӧl/Lj>;꣏>xy `pxs'O}طE}T;ͭa< s^ RmeVp<< O}ImD1"ۿΚTUai:$D`@Rv+(0Z34,FA|;]jnee'x„^\d~?eî"C"*y\,=}-‘"#jv**I/H/_}`G6}zlCkFW3hOh_k7K=arM[W'Nmf  #IhSCL:^Zv: ٽ<܊"rB}*4l-Ѐ/ `AE cZnuګSwEwuĻ|{?D?{L `ƈw€|7)nF]HΘ #r,u` zC 6 J8UqѪDjD")aD^D[PHN?Y nW"̷݅5Gy11I-ۼPy \,igeGh^r LIAd8`u]//.Jrs/}r'HL6ώmmSo~VL۵q_&}̌9i_Ic߱jEcXGGy5+ʎrʅI`BʹXC58/V隟@$WDg o 5g)0za/qFؓ1˱#{ڝ>Q_9iOx|b c?oM|$U P@l/B4틱$|ʗ >D~q]S[Flwk!Gmt`# &1ΐ5*_ ?BךWtWf > 8A@EWy%,ɯ+k64߽ci-וizq^=Vz?>a'j#&9EgWL85^;&_;:ȶ N`7?BœIs/߯rr`SѝBtUX!%1 UCK R=.!.ef=*OU*n B;ukmk8wEWN!Hz}MŸǝɪ%$'(JL;6b7:y$6;y{SKScuxG7;vtʑ N]_B;JM5ME R@!az~:jVY~x9h(xA}* ZNڗ  YRzLNk25qı Ñ^! Vqu@gm@r`f ԁ|@HDND~( ʰ|?t=el?(O@ՋʓvܳU%9UZ/w5bAe*\h_R_ëhA*ij/e=.Uqؕ$O );ە+"ѓY(Y\uّ˥ۙ9hC:ue]ᥱǶOSd/̥?Z|͟^JeM1wވ?FH}< ?acf a:yBGn\la_j륩6B*t/+OMi t_kjhx1#? ~_P{,_T% pe"(⧣v,G/+4 NE 𛅱8$6b~j_N|̯?L>6Wj@QǍ%e\3醗G@7.C"nVe<./WTP;X)yu,a#Jj95\ED2XUd" t CNO֎樂/x{O7.ϣ` ҇'_GJl}rMcw-dײ4hNF{:Sio 7)E).>B K]omeE" 9l{Y:@e`P"rdC/ P}AHWHQ";\җ)pECP0X`p7Aی),A&j- &.ucዜ:5W PAgqqy vV@#ȄH/qYf$1sKLfd峺XBs k-UC‡+S]| Yj欝6]/Ǽ6וh?O/@z;ߝ~`ٟ0z/nXkhrt5g4f:bʜwhogۛkB 1-Ԓi =5 @MG콵Ԝ/a@znCa5ZQ 'CeVizҝi,W @ wcxto%)^V2b  ],JxrEP @T>m2eM%ɭjס jH'9|yk ḧ́ -[aUxFjrWG%Hs1^QnfϘDeɇiuQUԿ EшD{b~T 3) {Y\ }DD[_wewYkwF]`Q]ё~߭h#ςEe~|+sI U^ =* eRhAXeeD8W]ei~᳾qb}f$5OlO7+9wrEϢ})a_އhoO))0w_n_MvnnYFPHwv0MC Y@t1P4кsDՈ8MBR[ ?x 4I̼(=V,?$vySժ rp9{eΑ.%ֶR@gIt/c bk꿺+AD}bwj Dm5ATe(`Af4-9/ 7ZIw_%{I2Ԓ{x×'&+s M297Ҟ{hæ0mt2o5=]A:(Hn!h{h(S} 8*!U7؈!D8VLü~LDŽ돪"_fo Q!1?‹S ~_ToG4ck  ^*סփւggiH]*鯫bǖxpˮ.ظ^7P L(UTg < `sڈs#S+ 6 Lё':cU7.t.t'>Zb$C3ϏZbڱ3l͚:nO.>{o4?N`- eA񪯮h ujݵt*Wl_7\<&ٟ%7&B6K bDQ @Y|ysWhҋ b f]Qfo*dE9}`@wE?$ۼ/-~k8I۝9ꇃ(=jankEBY NY+ђ Ci:yQǷ|&SMwxݒvfׄ[FX=Vjg6O1{'ǂ:v,̡f0iRڏj:$!h@Hex!))bPD0Ęn߯ -^b'tnDڣb_kYj~IGJ[ڲ8_hʶ)5톣,})+"5&nt 3"Z=n8/!rD^ WoY=SG6IG6M|`_6~s`d%v|{=;hΠ5L}A{w*#@#3?_zj>p­o "|^",NkF84j 6N^>k3@ : F.4Eƙ&,7ehG,˯\]M=8_A`80D7.R>wcTX%jzmSawQm~~m|mW YMQSC.ҐIyhc혰w';K<ƯOn1F6jưqhcfFτ0O(o Jfq}J1R@ ZkPvPꩥ/xa<\$1l/xR>0\ԚCΦ 51U8Qrr2~?eUw/C!pp5=M̈[l+K-O[&I[}ǿoYu F]@DshT75 4Paev =sAe>ڶj g=}܉^9//xpĝ'_;Qq?5^Sif g=k4 7P [+dnSp @44 .'-v_`h TշQz?pkۣW$קgx! <0P]ݬVh2 hs% H_:Hua7B7lM[yvcyϔ'xMUq]NiqAQ̘[ښ)=$@8biP05DžϏ׹ɺ1vgل G>{䯿ޙ oӆMfؤo5lѓ?U `n"PT5@aj$*]M]HoP򧏪bm¿#k𝆋H m R gO#0q_Q8SݻAuh5+@\uoft7s$ZztuD\߷yJ>94:2.x :AUJM h5}=DU]h 9Ѥmhl2vXWZOjf2%(3xAGl_^~U'l]pnw޺fo+uvNg|دMei'FqcJ)j!(RiHNQSaCO @zp@Xc>s;>7JQ 8ŚCe.a b|5@lX{_[akf953NGA-كzG/CcMjx>S8 C3`MC ]-4N HPazf3ǻBS XH|xqz#<j\T(r  4KAK_f3l5R@I 9sՅ)f@c:G@T$K'qώs$  x&:0n6&A5i0o]BB9bޭ!qUI~݋<؝2kX+S ht'UI\pY9mAh_97h JFx1ݙ񓿘5G]{F[=qN:e񗷓m02lg+ٳy>1l71A1=6j樉ܴ\`) C=q5 ȽM&m}AJ 7ޯ|r3%Lb/0G 2>쮝9Y}g'm сޚ[Æ}:c@ҽNa20r{E7-Ep)RuR~XqoP kltBLnڼ+{ql b Ϝ  ,3A~' sQ{߆ $hkXm&m~v$ n|} 70^mkS9a㧡==e}&SQVt ("jeoA)V XXQ3M{aM#߄G'l+O̱ЏD{ '@XUBː2 )[,W5TXu=ÏJrkps]Et·FWW9ܳ_w=~=./jyzS@Py BoؾC7]Yꐘz\g}ca#-?H;T+]/r>i7:y/.ێS.c=zќ<(إ` h2|& .NFrb~qhXHi1;lzS'$D_^;Q{׎ܼr-c~FY1j/iYNܽmUd~c4EiAfa&0Kk#M B18@) `ҹ*6s%WBU3)avǍBzAHH/W^ 0lq9[U3O ,z?3a:m ?] `;¡xaE;=}M_Z2B -^q, /"G+[0d-ƿpq'8L~yPEB&l~^v}8l9V;w05J|ߖŨ60k?|Wt$P|s{?ՎTf1`X#ZQqR/ ~5) C:] F4W̝G/4_3$++ Tz!i0T@w q$ξxCumO47mɉ 2$?9@92 Ӡ"* X|vQS~X$h(3L8 F^5qφWO38$@߼-Z֬-_:aۆ9SlbZڌ߂M4 SLL%;,N\$GK$0y:d&E~_{p+Tcֵ@^b(Naہ2O(¿y.wk*`()R| i71|4M!y'# XG [ !n !`^qXC}49n$. 1Շ_YFQ2 <;4Rvx?j0Ajv5 .xf CQOE~|da`0.T,"W`3gpUa]M鰞C' La|T0!@@֍do޾Y^ʱv 2i M5PkO|cжܣF$hF0dbU)P,Tܯ6#chw&xчV >X@Ph:mT6e)}@a:jE)Ɂa:! R>W1@@}߃\4Х()$ *co '&'| c 9^4V̓tٓMXGvjvьq s24MQR6 AOÀWhwP>a*$m;sxʚÑ&3!h2ޣ_x5O1~@ŧa0bH~J9:NZ<~wf(4x0c|$%Y W~~*(hA6/8݇@?Ԟ<33 | <}8{9-H}vTލ*~%vT<6WɆͩ*yT,)>p\3Տ4:ɖc,1MpB*TQ$g,5l&jl;̤E>8$hu<+(_R R@.PQ^+}ZE330sȮe];%FFbh*] J`PF%_x@X (My.pPPXǞ;0a罛 1!la>=j>e?!41P&I~=9P gJA'GX(SRK(x *|(*xPӪLsz~mrojk]2O}.R:YO's "ZJ TO9 >+܈:Mc(̻(!Cj WP\K)KsWrz| Kř h}^4"XkB (m+mvZ;˫F}Ea, z>n.dn@AFM⪬ ߟ;S'Ə>n]UlE@aN}[b23jU`̨ʂ+2Z, 1Srq%LӐ@=];jՒ,iN3 hN$6#7>oi58{#-vDy@=L= 093 zW[Y,8-7qV雇L?fx{vpl}p jL1TT/dD}a=Ԣ&eB%9Xw?sx* @>d1Y1vRB۞G0"<njv]f?ɵ}q(gP?R\-|4TLZ XYtLT2Cɀ)29cܡyìze/ucoWRHQ\<&L*y @6TKԓz21&n9%OWl!m '|BP Θ(&=a'l0Gq|]Djv⽆Ȍr7 IA:4ƽ6%yh +jΌ,tO\E܎ $GNiScO,6O/kaKD r#vt7Y-qS~<  /fe?[xy'iǝwdn_-m/*^,GT\2 PpO )PH[}b0H[-37z;iwuƢ?W,rŲ/vh܉[֎[ݯ^I?N({֮(NqH7 1*D޽pT1af>%Ru ?t |]&.#SFcvIKEQyadT671̐嚵bpI2&< QEI. h.d$t%.#] V7 $cKf.*bGcY!#9DWÖ,`O#m vn6;r/_c֡cfM+O/rP)NuH @@uE<r٠?A\s@4lH;ck_\;c!T\,!s>Sfb%p!+}trr281+ jvۘ  HSy@{{G6eFNYe{6/_DݹɃ夑z/sc.;yXb 3n-$R"ƌ% wY>HPπσ]wVw6a!ʮ,;3}3[jklaJq\J4`x~V8kD9cf^4mo S͢ d nq]>ߦlH@앯꫇Y]{uFWMd@돎Kg u jv7'wpjHR91H(crgDĕT}ƛLJ9OڌiB]?bh%C/R'5T8J.aӓ2lCa=Q+0`D-E@ hd [xk*N3\1[/V߶b`dnyx9PLI@GX\c*Vv4s!90 "v d)Ka^%eYIh%2h/ؾb8s=]MG2XAZOIhW} LP6@ks'i]:$ekyNcqqEvSEϝT"'nՄ5rJ3K)АaX@P PSJ/zo |\46|B0-!ă(:qz]3EeԒ:Y^2QS%^zvP!Qvc0( in(BO=9_ǤOQ1žؗFnYWP}p 3sZPJ'Gn88b/l,l,h Q|*IK~Mn[E5= buBG2>R" WXklMijKQN(b*a̘ѬZ>"BBwf<*N܍nÈst4h҇WfqQB h^؂?vFc: b DR'-"&=*6FE%ʔC F[.뼑<'x90(%ngcLOt) S}Lp2A6A0dLAp҈[~'%& 4/.R@j~6$'*YNo7?$DBϽyV 'E| J(M 9QQ;SBImx%P,ӗ|Qk"4҅s^$8 Q]EMok!^xxge?R$@)ؓ˓Ƶ 0DxHƋgq)ח~Vfu4[κjQ^^/)qNi<+% D\RoGnePAkrI_~[g mSr&Y‰/ ;qwٞ9v^Qަ)tw4At9)i$D^]%[d ){ sFݷ/= ]ѼX]GAb@U_":N04#U E~|=- Y$jU43s// bvIDMvp3`>ШNE$^0]JFD9yu.ƀ/9CY0$,Υ}BA2|%(b@=9LeUuDBb""l- vV5UlKWC iID\,`ƗFi1c޺K<~|.zʸna Q]UՐq"Ms uW usuS{e;Nm-w7XRߎDG2B+s+v*+iom`TZi%q8.EzAFڮ*rebnpNcSˏ^޿AOnJ!nE0'`kjW7 5zwF়c& $GmL7< # HyۺC ߜ`pA8 W e,'Y-fbPx\a=ML󯴁тzq3>>&$/`:}E68L/Q,At|G-6NWWNtyeW OY2 +Fnd!OP}.>n6UX@%* H44q a6MLky8]Fŕ*>^yҮG%NW;~¾Z7k۶[o- 젮DG* S:nD5'[k*h(HE5f*溒vJ#w\L 7[q2v:W3BL a4x , Q ŰS]Qlchb$}R("}x5^W&lc˚)ӏ2HLB󃫩%|\M/=3EF$^9VQ=ʌGBDfQ .'84`Qg:3J`M/[Y{Ɯ>B!φ& fAt"VB1 Tu q j;*yRxn@6<6'JGDKKﺞY@zK5Y`+EnҜ"ߜ;v)/r6pavnQ}*7NjWc=]㝬|tN˞Y}r7~f^ f^8T 87ÉXӼ@';e,G|.԰59ZY%Z榻_9t:͉h61K |]*Jzӄ>ukџ}<(yG%_,1[5T0үLȑ)n/$0rիB l0p:lOnU_܊u~(8 T T w{=Qx \=q ~t(>)iN#fL}2[rZzfw-7prJ _B(ٵ7 j|Wr*`uVqaHdUB5vUdSn90G 7 ZJ!5H5 HPɯJiμ]R1<ʎ*B@Oߘh+C E+{"4Ms11usא6U<.~\AQ\ߺh{]7{#Wc+%upLfQ-V-J칝;ަ]K[P>#Gjˢ'6osRVql- %δҰHosW]),Ɛ"l Zbkh7 l { xlC("c"zc> ',I נ}#A1jKm ak4= 5h/3ypP8ՎmD_|@g)10)7Ѧ@ceex 4h gDYsu6\]K+d>(̿Wؔ`B+rU, 隴ּaZod ؤh3 ֿ^aVr:Klh7[ۊ$K\tr]8XFA7ٺ`]nZ*zDn3׺rYa'fkyvyU!NuP'۟_Lj3[pvW/-X\V ڢ 6'ۖb)'-91g7ZSw:*$7]) oN7;-@ARHHl@ Hڀ!JOgAh]Ä[B(p{ iDu 1*~E;*P9*^eۥ^S5P 6–';&mX%Z ^0I{XHE,uGMAYHT*5 Jt=< 禑ӛ[è~OL#^sv9'zؤ}̨: ^|MRMwzT5Q" V~i{)m.|T\YRE~*@ n\IDKEKYCOhͲ FK+l3?+4p/ .+Z{XyEdTY=8T1qSc]zZ'g]:|Bj6գ핎G*[ftCęYwqWH+h$!eTSHɍ)t+ Ku7۽p-EUc`N?ݛ"VQ~@9 5Ə`*`,E<[)!T܍(@7u$L^;-I:po@דNkn+Ql쀘wU8Xd [%°T_D|3}2>}@\Hi^F? 2A YIŤ,7.pA5Ɉ5>iݮٲzxdɟksٝ%yT̜PW- aὬX`ǿ\tyiM6i_$Kj錨uJ)dGUn{Ɂ [\uUԡ#glm wyA[mmk=G8^_ne[[ ||.^߿о8oOFQ.my%zz )LO:ढࡡnfo~Y+~_~a>OUTh;tGZޙ>!j-4Y ^(2F9b6d0PK@cL A}l("{m+& ڦ`X@5p,Ҁw_S (H39O|?+v+!bP?U0鞚''V4A͏\pPQ䎵]_ O+C&-j]yS]p9[9Mqʃa%%af&@#3H͵297u=] K+z_N)HC ?P]`?q pK:ء{+fgOx &]bG#}'55;'N^稫I?{'KM f8?Q/޿YZ|Q4,7r^R! J7t1T1P:vdeR;xjg:פo!Oώ *.;xض{Ugvm C֟3Poۥ7~}@}P2/oNbDIpIzf}Os?&P4Dn=U z)QǞX'@U|ĐiF|] ;LR;Chr+Hpc^B38tP'^8j$&_  n@3$ X@Ϝ۶uZ璹k+!BېeEif`\X^X2~Y#WeE@ \Iώ)(j/4f;GE[FE-rfkV6 z,9zYd$NhH0Qp*R.8|93gTK)kkkY'Ghi)"5 goj5+=dd`<})쁓Zg%:~o=䫡ژI:7=^R̊p+[^:tlüFR׼uO}3m;g'`I79teb]of3V$6wa!fk^4?e7{4=NYЫbDr~wJ>|V8ڰ4rJH=י!# ĭ!bdAW;n2L(3R9n|au^cF/݁8KqK(qM@+kRRάȏk(6v5 OlfEU杸m o(-S]cquϿXdl6FHSgĚ:WϞ2TU=vm dOtV8AիgԒiʄP.4FSSSƑU%. 螬t+dz0,-ܮ)\,wߏ"wޏgf2EtV{ICUR;~nlllv ?ہ a3a+J&^?;$Q9'<(A5J Ieb(c)7)D6?#) |lޤ X80 7\; s,5 _?nJ85+2Jp n`?ߕTVRgw98?A'>(eob}#K~Y>m2^ځKV@rf>QQ/uҤ }t(ۇ7|Jmmto ]y课:hݱ/MxW2=jJYͿmvpe$DFvLу78_8We?tac ݹnWVX6:[qlfjQx詆J^L)y)hSX/s/F̣ζ~{,$A(((#Q *Ow4nኋOz;Xyd&Xx\#[['w&Ab[8ܠӖ%$yB  './#-wߚE`E`/\ ,\*w,1*N壋7Kd>Js;:+Xi|]GWR;yJW沕S:ߺMS^ZDsSg?--JwuB%Ho=(H@}q n!Hu&61LFYWYM[NQ;:A&&8[W OJQ7h񫝱O}|BF = ^pۉC}[`([ $o[YZ 4GEh$Ikpr?n cyaOW3&倶SW&")/H3l`p1)Yi,"nZR@{ wB#]1~A%鉷[*:;Az2 m!g#kQd9B\}ziDEL~@YUMݵ RtǃfƿqDIO?ئpϪm:lU8x증J$U^3kMY9%KAnj]OfyUA~qtXi]`-+:gvK Ϣ;U)>G!%*7!0'^\ZgC7>k_P[$8٤zl rD 9c_5*ba+.9 G0 pt?Q~=Ώ r~c <7 Pi<(v1C l mtp`3,, =n`@yz.)8zLJDۛjb0 |`nI5R@R1d % cl xPlfHy^ݶZNG۝(jfAA) ͅy)-ٷ ,_=u2eoV}A Y78-K֊3]<4Ux2mZQv9cׯZ_J#kMBg>;}#:h SӒ;;&$[{t2mC7a>LAfd8l1ߊ)J d%"\ OIJcA fKm+InkԞ9}Ja WY"^N5kwn%E{ZZ:v<{]L\ #+*2!mwş`+ k 0K5WA#kg][uکmwZY ^VK?-5Hw?P@n XgRg^pq)-zL0ᙊ'S@A2t=k`&A v'+Ȇ(,}y& -Qb6E@So4jLi<6 "ľ06# t`W}LC9LeJF9U/۳qp3^ 0 "g"J:anLphLd{e'V0{)W/|95  6+_+]׹/F, N"./#!M_I +;ݝݽJ?}?~' bӾZ:{kqAa鱑M-!:>ڊ[{lᚼP3-P:JKi/ThSo` #nVFFx`,=fNuUXRS!)/8{">>) FCV(`@q#3 LP1;L1#uYp.nJH1_ɜ_?B!:$ o>q*xjOC`I1(5za`pnZd3;XL3T-!G <=6d[GMĶw(O׾Ñ@"g[UIUw[Q!)vuRМm-,8$ǪH*e]uSScO^KLAt #@%/F:J`8yx/gn?o[9RR,.a:ۆi+=lAiKJʛ%Ni[mTA@&@]M楃Hث*]޵hEliG>MOu5;fyU-: iɴ0,/L?xg;sLVj"ɌbBZ 'ED Qy!玮RءY`ϛDRPe20Կ~Tźn p{:B FANց-/ $@͓z>! z2-Wζn&HveY4&:F;ni];>zPڞ!͉N6Zk`eDwOVWYe&(^lPO`s& Xcf}飿hSs/?z1sV 43ܭr %LxHς< [/niL?5ھlzp ?$W8y-y#Aukt5 @L<>@ 'mxEx ȷEro#:낡-cu6iCeed(CIc^^{rv\vD:v->ѯ܊eYH]'eayV? ='KC3Ioc '9vF;&T̰y-:hk ŗ-Qs+b'TupzKf|C2#@2}0}޹]1dPg )ֿ|?90oq* \t g5-V^qP &jC>g GC7`'\Wj˯;?abFZ+iʹw0J̏.fWܽyǼvnW'!N}]gA#fTY=HףfNDk _8L[D9\$qQKO]RIR rz# >0Nw3q0U:uj+/ʄ[$ۥX2(s &>l`H&`/[pC̫ QG-G'!N*C}-1> 2MU2+~G([l^Z f$` 5`,t#:D<c* }1q09^P 6O`ŹZ@0cb[mĀma'qm pGBBQYf<;[ >2m8-qX#|xI5r,WNo\)HM9D=GF"4yf[ 'e`yfʅ`veh݃'I_ )tCG)|Dh Cְ{,_&w-̂' SfӶ_&nA[3[wpڌI3|2mҰL5+`3HLOX;;WLe3YyWK^֗楯tLEu$<$ @ ,$^ P;n?oز@9\Ȁ`ńhny*_u/Q.?g m&!{v[[V5d+"l+Nʡv^W$9A1FEV #*K=S,B<V>.66ƶzG L߿&LMYfO ѽv!!GWYcY]h魯jc椡na`%/imi9QsNAܐ[@H>] th4P>F6p"mFar~;sR:|~?" ~)vxVrR毄לi[#"iۯk|xxz8P;C,,oȺtX3'cBl;>cH[)xJpBq Ncn&*OSS !DLrLsvH25?VeZd6SG;,1~Ê_сz8J&Xa\|g7 ZR޹*ʾ19P)EI.ؾpu1Ur6fF`0ǮuJ⨤5sP8VO6_L6q#I8ZfMs*|72MX+89D8[W!|۾D:l}@ǶIV=`vq @bpuM$#1e<WRd^&p6|Cpv;~#7>;EGۋ |@`r=mT(i llp`;ak!ƂMPa(* C SV l,x{2\zBPWPe*o&_ ]Au w໸@j]pͰm(,T2^Qƭw{ϥ\ Dph=s#jCcRCsg%䝞Ajs20J ϢdqEvYށZqx-/G 4rPp} 7U;y5RwTFڨh)+gxk럖ו5t> &j>zY6۳l3}D~szim#%w{PW]dW0L Hߌ ҩEDE qLp_xs's> ٱ~("Pe v4ra&:Qtg!"ҍ4W!,-kwCG˛/)1H*#N\~g(=b*X^\`5Ԟݡ:6t>ԇ F%yTj=@ /9:.nҝShx$jfAI#ۤYm8m6W/PdF{e߽7EQʀ$2(/Q$;:WRYɩ*.gf*x\MTצ޽t;] ^wfj!mKZfhxۅ(;%[DFPɭ^ (8"B[ܨIKڸlA5[yAS؍|7]h;`@}E2d}4eIz~q)v>aTQRIA@QN:]8 ^ PnL>p cz Vx(Ik_ǯoa?[Ee4?>հ~k #2 `ABK EPow`>kHS)*I톳 ~Ͻ=}&p-o2D ݋P~֮] a0*'cR^p ۘH0r;onԮш4rNĄC<4WZϞ3oFmuςLM9*'vׅ,L7\.y7p. `.}X*&sVC{Ϟ8%QjT3.)5Ydg,0̏fwcj|JU^QU3ViwYQEX3߻hu)U58a|Zzr'jSS49~73lRT-LfJ K=u&Y '/r )a.+"σSmDFN @6&0벝yŚ(\#8_?}7e?ܰ;]l Imk2Ęi\ 52@lD^M<> 0d53Cr;MfGQUI.bj9 !/ 4!bPu xX 9w;,r_MU@?/3x6 B+'­5;+  b⓻{Ao&r*0t8M_(1I2~y58)ڃ+0G=? 6vBG }$`܏(eYi\~PAvcvͭ Ymx 2⚎>uDe2|<#fqXIxid138"<&/,7^qTxO~a/p+ PPC<[^EVywyQEAx'4$,8^5sb(cy@EWDMm&*Wx#)&%Da 3+33Pwu`0} nhmoU{FA"@N+azX^Á]paʗqܸAZܮnms3{WkYoC3I";}v*s@(lArQ稇j~ :y'50WQKJNzߍo4+pbB|%,Hp+\6r 6c;[ȽWf[`VS!#|L °̶҇NWrTz;KKf$F vAlm>|AYE*W&Q%Q%G.k/Z׀k[fUlY*&-?/QRVZZR\Q`oS`]l[fWjWf]lQZT_ygԬ|T-4l=-d.IQ5;QbBx`oŴevr9q[]Qbep_ b-eL)&!PcOt'y|7N]<}ޫ&R5_l^B_8À-a` |ǿ ' vINVy'ZJ״"BR8oʦk*^)=ST,Bk=o羫K?mvM3g=:F\]%q'O׃m9{@(`en q2sykjҾ0@g UB5Q72ݻ؁qFrIn>(P3鷅!wr0S>-m c0s =2 gЭ#A+}d/NBb"f3sV0X@~!#@92x?Ht5ndKlM۲CV;8""484m]a]iSe]a@ϻj*6b"8vtVVdZ;*\=qtnp.x1mQ_\.3ÖrGPs… C7[hߦ_Lv!kZ zNZt=0p#pXs'#ٓ5ivX~Q 5SӺv9@O:RT[/x~ronS :9k?n9sC2P$V?יD71|2c1] <. @{Bp7ƞcZhўhGdJWiHn2⍒+NP`ȸfs6N.3:p*ګ*٘V'( jod Dw4c, xPxĦzEXXkW'ú^L pM1V^*F f }(#8sSx x+sچri:P?( 1WђU;4魙0E[:"w@a 0 psԒ:9+fV;`l1dlTk|/֖ 9/+;j)/Zx=3.+lq)D+Yԇ(i,(Db얕ڲ)|Ap2W󣇆v[QGkұVRQDw"ѕ&Tu'<-EIKSG-,?ȹ<#7Еnf{QjǢeW2vW1pRҴ0MuHE5̽Ԍj*NuJ(*8zZr; uenvy||cD7Lߡa20 tJra )p77Jdߊ05e'Y _m ?BcJ:a GOkxM&n@Nu" Z٨0@x=NqfTu$=Ҝ+o|젼#K>ng.{3\FƗdL/EU湵TVחzeXY+[)\qQV5 p. w 17V?{a_*u3eoCY-26`,ܔ ̒tLL֤8;YڧO]~\O?+9K"ҏ_Ye$6HBSlTyڏ7bl#ke0Jd:܊3ea? s=8e`,~ t؊ߊP{q|/"Jp$?S+,ؾw5 l7^637~'otꨢʑVjGW^guQWKv/{1=|teԮ9;{h:X" B\L]ܴ׎jvgڴ';:Z&GGEG';AM8@<>.[D\mS.ׅ%i_<`Y4-˴QVqپN^fi4O  m렭qٴw^j`F3/p1jO!rnG[V`/ :|ONW D[8+ن_Xuk?k>T t~m0}R`r?Nq b܋"2anBsV՞ = i?ugi#@gqknGz¸ɿu*N{kxz2 Yncl$}]Kq$ׯ`NTxcmZBs\xp- /U`X+[Gxls@Иn u2zau}EMY84]M@d{O!f R*;.IGuDzh/m=i*iu0H"a<$|YT߈A§ЋK, {\)WRM@ {R Dd5t?Xd`y vߕE&1QF!FwlOU ]ܷ+PD ;aٛXiu??josUS Azn:gO_ ?C~y+]>`.}2"4զ>̩-nK{*`+Qv7)4,&>)?$\|Lդ<*3Kbڪ)GYe{V2S\+A>Pʿ`3} 06Sr IU'%m,[ӻ1Puz`Wlnt! =q on{ Ӗer7مSOnz>~4Sʿ0~/1W- e; ?dAL˃"MsI[EDd SlZd[;?g|7|Dhg~8_??oԟ'~6 =sѴhGxw_njkcgna4tHsJwIai?Lf9v;ߌqpLs2E``ml)nr13@ȗSL)GnHdwmP 6V?)@.Y$~;h7>ug59a'kaler+ L$ Pn@"$d9-vl_6|Ȱ5be? FF| jx>{YsR L]d8nlт5~d׺@30(,_.uC+ Qc}Y}Qk$n6̏9sZĚ\pdݱs;/^~Erߵh۾Ż6f?M363ڧ~]짣4kKc~AiUEݴ3CD+7ץ=͙`O3n⢨w~H=nvV?=( 9h`*iF~,?,KIy|!#IUgEK7U+6/~eMI3î;݅9l'VC -?5xrD/0@ ݞA҂Q$oKf8p$`hуu|`zu`0d8ѥ߼a"~my9`&941- =sۥƃ2Ψ~TV㨼!Y=g7Z%3.jGAKWW>|S9té[٩xH纡aUmmhv8E<1Afcl9| HZIW&Q+,af`aUdz+#zu{YdXe9Z]9xfmu19;S'9Ӷ(+~$Z&eg j 5L@f=Pa\o~yy it M*Sxp1RʮX0+%o|Q1P-M侥-v23tPMYX\ۢ+ nGaj98.wcRl ]9 {i! >Jzy!fEAz@WP/a`z췴XqLFFY<ļ9܆c(٪ߨ!آ ~#Q`>FtKn  UDI?_9`@z,ߡKAr\buH'˯%¦3gx r;v:Oph*2su󽮱mr{蟽,qnɴySߛC96l8wߣ;]4=@bnv3f?ѠE/ r\Y:[9( gU ۪-ԊbMi C ml0.D3?ڢ]:;MO;yjc+$}Ԧ$,YgԤXY_05˗1$>o<T0]K@^_w{S+к.&*:&܎o o^?X6-{5%.]Tp祦/j{bVιm[ Ϫ*Ji[m+ImfmN^CDNhPsĹ$S<'_y܉-#&w9'l`KaKۏ|\zU'Lie=:5~.P4] ,@m ,֒sAMa }WBݳt=qN࿺`aa9 (sM N- T@!5\ҝl 6tUkg7%=7XE堄5s8+xg qF[ M/ߖdQS੗+n~uh2uW2v3 2Pױ>k \Hz{='n%2a(C~D!{vF 2p']upm5i6hO&\f =ƭL7p=eg+Q:E^MN,4 Ӛ@GH(;ǝ Ҥ0F$Z v*^er.p ۀG:I!hp"9h^ä/<W|Lhh @dH$W&k[pMwv շR1Q7f$ڧ;;':&X,D1[d 2[j9{V|VFSAVC(H2̦Ūau/־+LCAǽ8?XfNȸp1V@9Ϥ1Ĭ+fx=Y{u93,jăw T.v#J2zrܺ$Ǵ6a縓HN$:# >@.tiw }go7iֿIЪZ~ [d`ciemkgo + }@m ۵V?;̊V[\Tn04ΰn8;ݑ E ƽR:t 1c z>,0ՀԅۑRO7cF? Pםx J?cV횇u釕 pG@ $P>v讣(y1w K덓gEzt$@G5E/~sOEWn{sScs $v< M@ئkz8p~IՇJJ}TUD.n`@hRZ⚆UM %^33kc`Gwgg|39sεO kЍ}1pćw7B}XWZr2"CNFr`cHni %Kop[Y=wb?vYltି..#Ցy^VW^MWBY)1@ \$s_UaG[UqFAd6 dep}px<ɰ;9k񁮡�uo~7?z/,\% tV¬?1ڹ+/,=<ayFp3כNUA􋰜wےs % aM!s7>|;oπbtk^Q}ݯ%T@42 ڲڜݷc@@ \>dm\be7XQGC@#R760P҃`&;C Aυ2HAvN gpjeٞM#+w+Y=HJ]O41<^t V/}L&WFk7}z2AfV( (d>8DJF`b]na k[CCPjQNd4A[>c}T_b9bn?;ҫֿqgUؕ9& & dwFW^xĚvcrpva>-m (` Q]ܦ2/=D=ݯ @hkqiNdO HC˅<_ͰbP`a;P3V2z/%:lJ|06Ƹ[=R¯on=l<8|Fұ 3-VM!͐'yp{w& Fsff"<ʤժ zVRf C#KZL2oVa^ 3.SfTX;M9CX-]@ \[5w-ÕbrJĎx`e5e 'Ijop2qQ$u)BZN!j#ǩjίma_T 5nʬg4a +7D$$h3\1 tz ^@ |lpҳ4L7<L@ d#$"۝ Eӏ6(QecIa^mKsܬxn.?.eٰ&9ۋX^3g?(uS|#<+[n3k:ehiI]_$a.q&G т}C.JQIENDB`cyclopts-3.9.0/assets/logo_text.png000066400000000000000000007572211475451620500174260ustar00rootroot00000000000000PNG  IHDRz"sRGBYiTXtXML:com.adobe.xmp 1 ^IDATxw$usnDwڻi;[`0ћ($Zi+hD( o3`w&?"3+vUwuOp~ʌq#2c|`B`DѯDkg*gwͻrp>5.M[3,{ _JZ vc";g42e"ٙ%_N/yZ2M*M.ZޅDž3 źB!B!XJX aKX,M8o 3ofU]8 !B!B%!N2*[U2ԮnT {e<}+gќe^=tE~&/m3W4B Wy>BWŚB!Bq,f~rnF5%.=m9%GQ89y7qWzC!K-Keòqn .0.%:esj繥W^sN.r)gD>y/@|=tsmsOoy>+[; \ ٚתxY1c!B!B4YZ2XN(1O g%j/_J rgZU̳@ /4W\Krwk.o8g Kiftӽ#f7 H"B!B!2,)c`oY%_.yOaKI(\<{̻ޙuNSbB!B!5B.1M !B!B!ZSYڷZ|*gW;#.>s7fGfԮ.}<]yS~h-qK~MSɊ 9#Ay y.|MϵL_K b;B!B!Bg%cP7n5ח–W/{8N7Wiױy\ާlF-K9B!BTmf}' f.3<+V:ybm>s#7JM8nAt-Լ 7mJ]y;f/}wΈ-Y<1ąvq@dsI5t%b JgesEB!B!X%B{7xwH.oͿ*B!B!>KB"Κ-?1$ )B!B\/=y5qA?_|E <+\j50IMY8f:)4^5Y,P ;R7?[zpp*[.cj5V->F!B!6֏y<\(78i!.XZgdq։KXY*^>sIIW>` C !B!ׂd B!B!7s*x7Iȷ%}Mq'.Ik+wZ5k$KV!B!Y,:Np%"_nD,єp|S7C@RfEZw7wjkξe^S'oP4.N~W?K| "B!BD*ߛ%#Qx/o1)!8.xyEZj9߽:B!B!%?ٰ?.e8[!B!BqMBɾuc7Fte]'# Iȟ{m~7o?B!B!VތZ up[j3/58_oOZV$20"3h\]7 =wc+I7DDDX5ysyxޝ2i-ʿB!B!M*@1? ۪6af`x߬rM!B! $in"^#+ [jϫkX B!B!{D!gsUꬱi<!zC~|mg"#VI F͙QzYkoAB@qE8&pí^k(.[ bS_R|-B!BȞKkϣ:2jxk 1^U96 `$AeA4.@3ep !B!?$R@*9]-05~IUcB!B!b$ yC8w Hh\`uf/"M'݈n=ɌLll}KxCހ9IB!B!OZ]LwT^*QU6WLxҏ\^N @(` |qHGoDF$0"1/~.~"|V,n[h\{;ݲh?KH!B!bY/@\ qVb]\5h8r혊us iP݌%woo%R!B!6Rrʻ,\ϹæY{%=+cp5&`!B!X~\fE[דPlhhǼF@\Y16Cn>b{ ܄ڮ|*VJXT]vzvK^7Ĉ@vZFHq9ڵ 5B^e `pSqW- ˫}V9o+R > !B!ɂF@\Ӻ?&2 <0kȶt\Z^g2.`mQꏉ X ג4n"nZG7fzI앁w< NWWnP}xtųE$ B!B![#С@L!.g])lByqn^-~F ,"3CSnwo-!M!W,&D(ٖحHK!B!?6,zf%1"<+ǼFֵވbyd b;ecxƳKY#.'.тG@ZCdz`c`D$ Th#""RGafUV1'Y2 鞙zF" 5$sZlѠE E*8wXS$ߔ2<[!B!XZ.$^bݍ"joq.ZHO?G-ie &.35H*gz329MىP 12"ԣJ12!1{3"3!dQ$"E`X`#6L%I!"1Zd/%B,@_=377rŵT<݅ LPZ69%3߈kqz΍=/{얍{YʴYB!Bk!ٷ>D*(źQ ޴.{LӦa?UL4^E" ٠ lP1 LDR $mT *4\C A!a-Z 3D@lʌx/b=񦇘vP8$";dr\6הv{ $P!B!#Q[#1'ދvĔ' ?L hn`DkDd&~" 1Y[m0J)c "0he 1 M6QR@h8 "$B]o9p= @DZ{D!d: bpYJ\F !B!n%[UZU-=/LAԘ*suC5ZC lR iHB 4?D2!RWm;$?H@E` T1`P5N-j 0 +t ml E`̦v3A@F2>ȺqBNYjh WN"*+8\!B! bQ(nus - }9o 2 Ua5I֢e]G\kN@ miEYBDFH͸H8=O0+OmA˥u!B!BfMa/9f(l!|ܔ=d+섩-kX&̎{.u`[2rsh91HH1Ƹd+VDBe[L`F T=р4W4#(_LbDG$6p늵P#H{ưad@ Űx2^s04 ŵުB!B!XȾ97DHJ 3BDBRlAafVJkCeY37XDCѪ\]uQ\@lR+Tk/AD M,Bl+D !B!'D!X 0"XK2[IW9<8Ȼy9#N_\w "ư%\T̄,?Ɔ1;B =[lCxlgHLn̸1~F 2[Ȇb<4tyK"EَQ& YݹgK$Dۭ^.Ȧ1;ݬf0` ̌5OF&̒o/~)EB!B!D!g'0jq{ &Or>.ٯfT.xCo8|ĉg. &˥dž p O>w߷ Q{O{(uAFTRe0k}W~>mii'b"GS׎Mًg_Mв X3h<7WKiC)B!B!D!gDx20@{_3B}{bQDOB`0P/$R!@jcl9/z6\C_0RfDb-Dcsbd([.imx<ӱׯ޸cKko[(`Ab\6-'|3@KqM%}- ^l)&&,љ`33F$USKs=AHqJq實B!B!D7[`^?SdW_?S/m:Hy +fDjTh)[GH@x ;wz_~+/sx>@$N3RY6%0K>zeq$+Vvo_9|Xd+d+D"b  B`m'mk.zȑsvڸegaIzUr!gg*I_}srl,VQko\5-=-V&5{КRL%0| &0s-q [.8qre]9#Œv@FZmm` mMT^rN$›( !B!ǒ\_]#Ə{K:addD;ёp*wq\Rp@|??/ൗ]6m[lxwٲ{]k0 \..@:&1-XzdCRښ^zjtstH$Q?Z9]3g~:r:)6iDH@0`dhOϞ7'a"P*_B!B!Td/ӵQ7FTd㆞/RɍV:tS#C-m?+$RX/F ^|ɩM+`掭_rٯn߱@ amkY?fؾs?w@[EOk$ՒJwl]݊6PkD~8BA=ԂVlkȻ2>Z.e.bga$АڀgP0hZrkVEf !B!BP\*?} y9tX{13jT{^xHvrg?Wm/ M-sCT@ #CS/|PS]u5aX7pnt23 h']֖@ Z:OoHcU GOf2?M-8;lǁ?h}H\Q(Z,e# lПͦj!3[-n/]NI+B!B!/ 󽊯cU,`LG3hX"lo…#;3z͛HP3ڵ;>=5U'}}D<4йfwyCOGҝl [@ =K;FKv _3_. U+RO~ɏ=ѱY^+oT#X~<#4 Mُ51?{ؕ|{N)rZM=G?kZfQ-zK/=klooeuR4f4h*+|\tx A)DX-./nGDPH33DDi06 {4x?6rɱ+g_[4U$B!B&R}]pޢ^40Dhvl \{O{oo >cǎ*K/޵-mz}gClٲ)X.1`Ղ>3O~كύ^SarUl\U#xg1M _~/w:͸AqnOd˝m_$Ur5h@=~mDi@!7Ex+xמy빧^OWe$~ޗ~(֞U7}DvT-{W c53Clr 9k?{b~@L&ҭD2:{Z#@{gK*oI'h( YBkE]X6Z%2# 1#B6 my{H!B!BJofG˚f/|Z!3013 Ts_}ҥa6ƲU')?ģ/M:6Dl; '\{}sm79k46]{cx =|oКi&/~|펵'YS[>3P8clٵ቏>ֹXXFF j@ܔ>7xDD>Z!;^W~;]QƄn [ml)8ٻlj޸!䆧;6ܿsU"j㡁0Adb&` ˦~+*j$T6`ЈmYJ9;(ґXkk*Dz:xgW[%퀅 I ̵:YCwRq֝bm_%D[C36ܨ.z=rΒ$)B!BxţHB5,$Z^= D@bYi;l֋'?1vj/f_Z?[o֮OƑt];>ɟ:weX)fm"B@VʶUgg: Fⱎm-mɞڻZmG9lQ&H@ IX~03#hF@63km"،vKj|y!_J2j\!B!X>^ -hlea,Lh9>Sg`έwsg0`2 O'+ ꁗ>ٿ{{riԯ~棿,tM<AV2.+^T9}o/7>'_ktEu(3Q~S{uxiͫVO~eU^Ddp\P\>"ۨw^?wl{CJٜ&;ܵyk"nM%-6:"Cju'ΟڶvӆpUȺdRE=̑im$Bӓs>iGkL_{qeλү7um%LW_Ȕ|<~iKկ^8ҫ‰{okφ-[ә*Uj2e SmT{8n+ 6)OYcʌdOʉ3GN8?|J#O>ݷںfx*e)DND fa6pJZhOLd&}/^:9;J/OO3DӕN^" ,D[d4괶ۚH%H$ `0`; a$$`?RDD |B!B!Ļ֊Bb=8:!c7tfd9ZMKy[z F& eiF6_>zlRУX(6h4 d6ڼnӯܗNdAhJj@decx2q^ifJ߄qQdA~p$O^& VK_z啣U4=;{7wZUD׀1X_RS@vi8=8 s.OtC#cWVݛ6ڴ.c;:!G=ҁ7~εں*Dӗ3TìrybjǏuloh۸}}=t}wߺ!۲iz13X@HK;hM?:ׯ2zis+U6f+X1NdO<511199Q(Jv2K"v- x8NSH8֖ܰqu{gk-ݑvBX 4q`A4yTpZ.%̼AN\d=B!B!2QZ ԃ}LVM#m<σ3F$BPZ̊FDQ_7!_':NLLjEk?95T}8 X6)4YPĒ!@2 hWOLea6^f/? e 6|.:u1, R_? !:UҖB{O>0e;\H6vf ?o_zus=wٲ5tu%Ie5iPyvnrC?N"DNJN]<7O<0Q){o۶m?;a; kExim$ (Dʹ0k]F*S`05+Cr,Dff" g򭯼poZqkI%BFy fxZa ~~X{8d1\jEW]h4Df`6l]4P.q`to|QkڲkÝu9UGy#;Ʉyo=+9OݟO>ӏĻ:џ0"?dI~1@ }؝~:Rhtu㉀`$UmB B;ؚ/dm" t23?eL9_-۸e;wlܾ)ݙ$BʶkajU+[Y;]}σmC `+\{ET3̃SG@ܲ]V]Ocg~VmYGW/^(f]yw.|NLRO L~<53\9CVml!v4#uVjJNH(}ko?ssgyMҙ7M _oh$JNB!Bq}nv4~oy?xR nR6FҞ1l1_IH`X cDtR@d,[_5#w-ޱcs(d'@?WtȦ`&&G!($ $&U͛翳/\V88O` * -bM=syg'F'V;yUm@"LmP"539 @)+}.Kt ł̆7oOooع "2Tt8_OLrBqoVRp:Lwv҉D"ݚJ$mmD"JC~> ^o-1 !B!7Mf@?.1l;w?z6E4FB@ÊP x{ Y|A±ӥ‰'.]+\2g/={=]wlh*F{3C:fݾ"T AzeTF245V2vp葧?w~s BRL%˿ְAw l%Q󻤨 7'a׃jV}o|wh? =푘 :E-e8ٰe@;h``|ZL@P}0jIG`*ݻve[.J851'G'jubtJ%?=lNMY.֒LөP8G-!F[qs#m ޺1%GV8 RεT}']UrX-GΜ8595U(ƆG/_☰[%  3rs-8V$l۶{F=XhB!B;G!W,Ջsϼ #?+;'^eÄwA->kP lP#c400Xv jC;w<~WFG??rexdP(ݑ'U?~>p՝ qhB`,  ϏR|g^jVH%Z1 OFl޻sK !x|AV6nx7Oʧc[oJ^q"?a쪫.x2_W=#I4EF~ ``fsAz85}ll슆![Gv0B߃jW+EwNXuύMMN}Ʀc&bɶ֮U݉x$dV^FUsMa*[f˹hvburVD2hI&Xvugow0F#q\X][3s3[6v~[d-`6f;޷5j:92114V-TK… gOL\>Zrytl̸PtСCNuO}w߳ŲyO_+B!B 7kujonaKJA!NG Ҭ%!7:@&pX/V8@`{w}~Hnw̥Co}RKCL~;7z"K&"ȑGO&l>|kcmf jrUx%70sV+B!B%I pŧp0c aMi˲LPaz~tk*fZ9)uZn}pWn,^>O?WyG>9GX`hm_v{?ޕiBffdCF5?ma` dK,p̛LɎy^iM'vK4q]킢bQ|āWMg#|/|y!VݽnXyM-JT< i&1.еa$zn{'jiϖWnYQF #Ek!PF@DBnIEm rhl!% RT\SuZv#t6ў-ml3[-UM/3?| 2Fd2ep+Չɮ©xz*O{~jWu˹Wq+JflR%S›StD--mw#`,K&Ue[~X6aF-הՖB˟ӍY|9382p¹3w? Gcv|{;vwr{Gzn d[IB!Bc&E!Rp`7}wEa0`㘱ɸ$5MIc#_,;tLpʉ?3N NgO]|g/?3pwy~q׽621jxzg*tmۺvӊ6O˜GW @3sfG io+Ȟwn3_};K%׀!D@Su+ʕ5֞Tk[Z{6vb Ȧ3[<='pܑS^}{vohI Ń I3SP\z.(wBfժ@ H&ѥ̀Tk؈Pٌl9``Fnwnvvuŭʓ#Bޛxlၡp ‰;wCh<Mɲ[::׭|DP-=z;40H@Wܪw`D&'&1tGH2Em Z]e{Yv \<~`]ox:ʥ?x#zͦ'r}~8" FfB!B!neD!)G yJv,rAg1  "6"6-53PTBA  Q6XH53{˙/|w\:|Mf+eR޽yۦpȶ,Uñ0Z?F/-Zw;󎵩TĶ #3xMkىw<ܝ9-j S)WX}.f& #/ eLN2lRd3BUؼMwojiO @Ffd *0z%sGv[ 10DxkΟ>〾cm֬iOFCu˜j`c;ږPA\5lZjџu/k9o h8ҕFc\x#Eϭb˔xv duKUukCW_Dm6nE"V^}+2HTb%_O&&&.2O{eP v;`%O&,ǶB)3tZ8]\}ܫA5ϋA|ni3;9, B!B!Wdz. & Vб,R)#F+QY]dr ?r;rGF&xDR8w<>7l[i$n+A+CG'ϼdq";w_'*fstRP@k \ Z#e5ªWіeѵWΔh|"#h@ 0e\\ٶ" )չ{7??ͯ>C>y{tH z&G]) M0PPY) ` -hrdD`M*j!W.FbؖRDDղ9popJ$L' :::pwgkGk|ժt:HD(9 @SVؘ@bEg" Oޏm*ff`h 0:d4nbT-zlqr<9y *+Gp{WO%վ+N"ToXʰўg\^P/7+ SccB17h} Lf2ccc B|bR='d* V{{KK:qjtKK: H4l;lrg ^hs_9̎]2`-,`DONچ0k4uKj'3cN 8a[٥j%ޒh_qc,ɶRԺXîUU ?|)79UN[i j}߶) +g83WؑH  if@/|A1p4pP0uۖ|cl~nf="M DӄB!B|4uyٯU,^ -;1i@ .-Pw@nR9bl۷eR6d jiV]ST7M_{gW}ۗG2=?:s/%Z~i;+T -E M=7Z" 1S\))^շmض?ϞxszK V\ױDubplar3n(ţJrL6[(q'ܸɧ77*ܕHI[޻g_xCnYutl=>/zU?6 *nLotR}Y+CUmm0tqlB!B Pu G6ћlQs8zX.k *n[G(e=gk751Ƴ Jxxσ{ydSy^ [j}o9jڵT$0P-ڭ<4xelPvsbn-6zq M'1"ժH$H" BlL 3iMns|R*U*ԩS3,kF۶H 9 'Zbmmd< 'Zݭm-EJ)Rkɥ7`UOpm15w$B md6 gذ2.e\RώN;rR `ST(p2 k{7)>Qh'hURڸWQJYʚaySTږ*޳wc8uVKϮgI$2΍2 ^ǧk**G._︭;gɻǡ>HSg|'/9w b;5ϯzZoYu;vyۚWۖ!B!+nQL,UGrn  [b ~aB5=]zH0gUP} mc/ZD" bỶ NNsCʼnXC={6m[ZX] Y6դ3챩h/`m{W_}|kx Domvjr|PuR-A"DFfl$[췚}xյ9CnC{6? a`*CUG;شuݥ3W}˗GʥRPd̚B ΎMl۵yۺ?=& G#A>[sIsu6+Kr018N($"\\<}7֎|YJ) 2A^ G']WWsJi*SʏONf2پcbQ)" A۲X, zս==D%v XИ XƦ,I*j}YlDF``b8ؠ֭3l3cm{cLx iBx#B6̺^)sjӚe]:WΞH%B0z|f3zp;tpd@`JuᳯuwNl]YFX/p;wnomܽ=[ RB[\\=p@Ogzvn9B!,/r+G@"dT,G6\vģNwHdB4"&&4b'dcDN' =G;rq|`o?}lߡ{߻1LE\iTU%=k_:v̱w6u\Ƭ_iEÎttdn~4Rfܓ[^ rR6fʨ-(ٲzպR-V"2?79V\( vvvv5J'pP1V߉+/bp(`${!`(,7H)F0ƸHhkrψ]6GQPmC5`k r5[T67\L /_<]V,h$L&cTr5T#ޖJ@EJ)e)hƲU#Y3I16MŁZ>i( 0MȮGN9~bCo: ԲkejvlDμ- t/ X\η{l_D[:HF;mmh4A;RU2f"zog/Go}o;Y*U}'w[pUzlCAG.rϾV(g*t>{~mdBq}!qxx\X\Ϙ@0@(SS]hqHLȄg0sx2 нjMG3/Vbߩ cCK#7`fZ􊹱&ΚJ$l{ݿw? y:` 6;@bG/ZOc`0{N %޸ff`&qSpa$[TCdžFp%vj2s+.ٶGH)ņ0(KiSKCf Խ#YHSƨhj I'=꺮1ժKBqt|zSd6ʝ?7U,]yƶQHD"P[[KK*ўiFx8,,hꣵQb};捻y mΓJFl-u@<yBAvG$crM.cO(__/7M!Bw XT&dz(pdh,֒n?7UDgVbWAMm0ק[JG*A-mX;)Y z[;^?zPR州{f{޵n*;Hfe8 hW F[[O6xy,֞޸3" Kb\k=hccpv<۠ lb2, :Tk\ʔ*B}˓Qc5zu6gTr=yZW515U*xbbT23GZlFspB3r}Jm7JW {h!)r,'tРacZY ڸW*^Pȗ Jyrb"2b:0^s̠Ec1$**861C۶ ͝;{qꞶ~fd $RR*e3R8R`ERжDz@+@`  2!BRcA-=iO4hfUJnX\o*r٩ܾjmq{k* ֎d%ڑNX(lR j܈(460{?""u /nd#0͖Z0FWܛ ݽ<߲q}/_߱.Rx%;eFӹLfl8ԟ9_yqlGYO+L&d&r. sAWkײTx$juV(Rv&hgl'_áB g_q%ՙ9פ򾪽/2ܾ̂V?VnHBB 1Bx3=}U2]*}޼.nfVVw!ԝ7nܸO=<~<|׭תT( B:r]*%Q E,G0ⱘahIf+\cE\jE ]+kF&nؑP 6[~K{v=չbM_3>λr;$0y_0C[ +&_XΫ)~+[`(xa5d!֒d*' BeKXلbRYKVMeE"nQƐyqv]_JMӑt{[Ϟ:x,#;{\j绮K1De˖Bcg#siNN;9JMfe_R }dldacLv"J넉H쁇@S5<4cіΦ LDv?'>98v~S+?>ww4P(篾.eC/j BP\ yQ}1Hee">x_w ⚓/Zz+{/OZj BP^\ Y,cA6 =(fK7%p7pLd864M94V*Ζ:0?ʅ!+6"vA$$4I$Ry$)l˖ w?>E’†, :VˮUV"(N Tѩ$Xi$YFBbe9q4,aTgm-D2hnjmG@k8,PәY-q!|_H"ߓ'Ke\!LMs3g>|h|n] "k\@Ghx{ҊxҺkO ֭~'l~q|%[nZkFBMR$'$B!&0 y{ uLfKpU} \ h6ĥϦ欲xSpz߳ЁzHMLrږ$+~/[#v ƴ(0̂?pТMƳDuCH{[{SS[;牝Ӟ5`OL-[DwI# !Kev} 2,F# 4hh\=Qs"z*ζXt6r /*fv6GGO!#@B =8v%?.TR+Od-DD$l ƈu,kp9ؐ 1\$>:2h?Ab gCML.]ڙ|r]/X;քOZ;M'D.hܒdrr;o lx"R8l>38zr|!\#yR"GL Ӣ)<߶@\i?ϏyG'W?yq& *MsZCBP(#B.tvv p8mF`P""NLvtzvLWR@kux)`"V ]0Dk-ꏗ#mjlNwbQ]GP Z=_"s"E!(~ե 4Qs'eIOH>L=a$B/_*#0mܴlҎ7ݱ}ߎ=O|N2Wn[[n\;r1 eguE@ |i嶶dcq YEG 逨H|K4f>eo]xf=5nK PBwr%r|`yE0C8B@9 Zo |vE$-(UA@ 5İ00=gp9u1m{[t,hiLB@XlXazB8>d8jyۍ!ٛ$$$# A{p~@Xɷn]wRZZ7(z8}Gǧ=;3ٳbbf<=2j!D@(*0ّq#d,x|+?QTy x;;?QKqYwoMjA j^zZCBP(#׻"$%fS@s]@J1\/^pi55e1*Yctv7Ǎ3_7vro۲f|q/ZDxVA;0rksI w_q/ȳmͽ3=N=1($b+v-2 S.>G 9Tr {t-XgMxŗϜw ֯]w]-_ک1B ,B8 DQ]ׅYzneh$t.rgKFi%$"z!IܾN.k lYK"\-ҩ=3⅜%HЗMf^31^*yTvgҞީǨa\Ɉ, ˞@ [R[+4(QYPsRlx8LD:6\zyo[s2lj?KHΥ ̥[7onii'NAOVdyy r>Ҧ:@(FH$ tґ"XI).2/$`NVWId__㣮ոJ~IBP( BaTyyrJUAfx@d.;+\' ~cR\O檍+"/*?SPU D$$;¾㩒lˢ7ŗ9v\.>|G'oio`B<;S82vFѳ/]!h# I1FSlnkYrɭwl>uf}/Oa7;6*d_vzJ Q n]{<[OFۖb)hL-Oˢ|IB C]\r^AjvuwJYMeZ|Æ[ׯioN=993uzhXuW.li#~<3f!ge 542$Vgqȩ=jKCrez+z:|gh45ϐρ z04WtA,lƖvMZxնEѵXQkDŎ,+^hHqږx"NrnDR JytUǚշZ\"g`p9x$c1&B&DB}+=1j8 щY׏l~_J#jA BP( Q!/Kӱɜ0K0RZ3Qv eDBk5#3cTj2@Pu!3zB[vtwv&9zu^q^U$@1C ,i4:S'F'>⳻h3֖ek:ό;ϵL XbT.)g\tő oq24:tLM^id:R4#D9xزZ+2Έt0{o^R>W8=bnrKapzv7\4̐ߚ*  (2WIl6BPwfƧEw^.f)Qeq{"zjR(.\}x4dl]nΖpECdEJywE2DpJ]Ȁ1YA/ih6RW- |gCij BP(?(82{|_h_vxfuGErNjEi5MAT$ٱĺ@G);O@ I }}K{ ! dycAH%FMF^ؿ33S 0=Ofά^jvfne$ř鹨[Hv7_6[?|7rEC|RQܮQu#m=60GY9xW WW I ȒUKo)ٽ_~$;2*C>ȩb9Eb#3Uk0!BLjmmy3,!iڶJ Gyc$A {_(y;@(EBbbp95;^3p!S/# j^FLjȻ;]}r> 5ƴ^:M9MpwG|Eإb>~Y3FL=ʼ'⓿~/G<VQP( ?? V[G_|ަVL8R ׸kk,tsdDG[ܴeCg{H4Ţ Rr([m[ 1C 9_#3\<Df<%4BICгH(;NdR/tF|RTzsfCL un$<@dU^)OeOYK_DS}+^ɀ҇n,G)$OKBS yg҅OA" :^sEtnl閅$g*$Q M̞mn]m1^lHg(d7q{OYܺy /p=9c<IRJy֥+O<ԓ##vŤ۞T2%W~?_Z}-eÝ7_~_M׾]O_{j[* BP|{_gև~EE*gq9?y'@;zr9N! "VF4jl.K6KF)A>=o=\a7߾tEY,1`iz`" @&h[4ZM\ #%Q}]VؗB0]ce5Dt%KΣջ؂Ѷm۶}O2B17ytWN ֭qXNΦe25Btj6ƍD4u@D e?p2s%>} %kQ3V<3nwxÚ^3}8сchYGxXGsqhL#rv.RI ۱̌7]"- NǨlm'wl 5bXsnkueZƉz3"c!"")kw  8Cr3$k__*kmsssc}wlo}twv;|5@`O~gVFQ( B~q_]n?R(*\ldW._k $F=!1H_ ‰(C/%X׉\ִ7Vgy[" e5v#+,ĊӪ^U@BB ' M YcT]fDf!$-UDTQHamე=,=#O̝\;nsʆc<w%IvgFƭ|4DC| vGC px 9+37Es7]c|I@PHv,N$hg˻~HRbD4^rÊ.w<ӹ, "!@Hy:: ! >&}"Sއ! J[ |R%:k*T$"02R$W^:u-~O̦FG'W5W0d 'T| ].լ;y \|Շ_l|=\kݗ?KW{}}H|u'x߱g>oO#ϥrqi%D* BP|^>>[THvQ52T&<[ jr{SSA$9㞇+cX8 EDZO M('M:@#\jE58&9g"k*DMKƵT_@=T湞Ix.y\o?^N\oUW÷ܶqwl*l^ZD} ;vn ^4HXv= Qcx$CR"}= MOMNy\n߾M< NL>}ʖD$ǶJ٬=Re}͑w6GBDDsZC,k d5pv߰-I)x+mH_@ @$ WH@D@u٦jH5JGg AdYe;(ʠc&V'ηC[mɐ!֪eu^nJJτP>Wϼ6[xG7ݷn֍ ޟ篣g( BPLΤ <-Lf[J*ڨx,tâm,&*OvF]!BhumƐ1FDuXV,7T?+ŏOXKz~|G{crÚoʑ_N5ڳ|6_jHDU( BxHg W?R!S!뒠HJM֎–lePaX83_z1?i? wߺq׷/Mds}/BP(ϿQ /e}>z֮]rݫQL BD.%!CllJtu X\ه~zDinfzwD%7JlsMOxW.$c Ԥk-n'71 Di-e&"1{$xEdQ_!0X,*:/yj Q$z7ra zz\FؑTkiC/Jły Z dts DP„tF:#5VPK4~-$ 25 xZ#x$=$|I [FOJy&ڛNѸ(٠{FSU!^.3E|RD.84瑐 }I9A'ɺ>šwcM ڡ<_.nпt>$n ݍՌd"ic2:;ifq :%@n, ֎DXE= ~F4M:o?~hm?Eğx|-ǯ|t 0 !P( BP(hae}1Knnf,79j%DcaBVE־cO֭Pr$bywϾ~Mc7nxwyi$С뗕N4¡{i"^c@'G~ 4/j$$ %,]$J| ֊?\ X/ieGRTk*K@`@@$@ Rrh01Q4N),Coi@ܾ1!olN74FM7pjht"2w`tx5N L$!#cK@@"<_:"։k:'% !$Xe8RHē J9.8)2ٴizjyxE;Q8@r}jI BP(  ջTPb^'#ݪ)ٴvI 3gVHe.#$x$nYGm?)Zp@׵F[6s2=[=99b +H& wv4YV-kVcKUìgETҎ+6 F.>[:2ê!= $:F.*"͋tTI{ @m{ {'|PE441IT.Y3ccNd2="QeU*/(.d*$'R{?n_S+biO| BP( Ȧs eʯ~d[!"Ò-M @~MJeWZ.fa"-9/ H:=7áX"w׻jMLtl9aM;kr67<Ǚ{ =0Kp7ecű|Fdڴds=<غ5Bϖ ǧd =6>QXvCcjjj~I)eE T7s.9UH 3?*CĪ]Q%6OР.kd8v>qo?9(޵~?/r ^ __7tMBP( BxZ}A$n^LD'_}HrH&S3BHCgKVeK[٥׵!"b!$-1Δe|3GXs{˶o^iU6#3sDp ,7~CSÅTckkC$n*,( 4 C3Z$,"Og3#㳎Ţص #Dھ^hbpp|uZ5iLZ\)&FX.g,DQ@"2jEٌ> {)ȣR\H?W.\|806}zVE[zb|ozW]cʂgŚb|8b??g5Kb+5&->HA@  0Tb}n$X*+uy3t)-vl6+;^ڜ5O֓|: }O>A/ ӿэkܯvKyɒCu$lS( BP(o0. hߴ:"kik~æoU81N" "I:Xn1D~[oa"+&:/G%c醑˽ȳ]'muo޹mDcD FdL8;J9?x%/-IR^[傟W^r]2\C.}k=o:z𩑹Cn!אXaU1V!jiQHB~b6L4<i{0= \ȩ\i=uT~-б?O4 ß{~,>{W,|>})m"gީ$HBP( B5& !{~W}+XP1 zի939%&O֨@ZLM97Q ͓H(bz.=8Ϲ:3f)I3TI3Ҏ!{Hw5v޲1);,+IQ;Pr@*|_T৺N D(zml)2 4 u5i r0lt`[L$ؕi+B ?((EΗz[@0ʼXKzھ޸vna͞'3{.<|j BP( +U! xw>]3y{j $hL eX ьh]+-_xyN?SKΖ˶;:z셗Nfxc懶?#w-Y X &v=9^c]/I*E^t˝/J _0޺?񁡑ǎػC^a޲e}kKPH 9޲ţDő9QvIJyu" DzNi7z6-飃ddCy&G535l/4]2 $K, *GAIyŴ4<{oqnd͊;P( BP(WUEpqy:s4%KLM bv^b@PHX qӌ$}4n~}]J &}5t{{߃߲vx2 %voRץtHϞKJ dݢOV.H ʃx..ޤ/AHx^nuFO.6nXӌq`Y&td@g|6P31;C #gc':XGGYnYU>vP>ix"Ѯ/[ΕǨ!d~zE@+N42=lx9޹$jS6|SHY ND%Ҏ&U3Ӣ=$@V"b6k^[}1sL*;ۛnXL BP( v/]=ͷݴlBx!0eKds t^}xnǫ{+IM8pj]G^99dqpӺyODzY-*!0]VrEQ$W?@tkjzӛ8vDd[#tV G@Bȹ\q !W9O$p\g\<ٸq kjNl\ꖝⰾI_,%fvFd&U:7PQKi2\c8/ѵܵ5_|zY& aċ Uc Jm sf!KMk9x7 nHX$Æ.֎ⷯ?맔A7ݢAP( BPP"qGkKϮpC!$1 L @CDبCH)$y\DtV48\yq )}X'I^O~aѧ~ܻ'BP( BP( Ybg:Wt;Mo߾ұT)==>pr7$"ɀnH\C7\pHSg^|ǝZ u [9o=>09okx:[5K9GP4.ixӦgwݴfI(;7b ЌF7 !b gA)[$H K-FJRgӚ&95nY6S8&53 ޔlھqӊݝv0them$.ˎ+ӓH ;J<"y+nݪ4߁qwnM42I$d |a:j0誸1X:5PRJ!AyW ~0/q?s/ZpLo?7IBP( BP\yU r_5/76DW}ť7h-QĈl$bM=-G;qfzŲӣN nu:V-A>(!1눹ٙ{|)YŲU6tlRH2ŒF4::;gŒv#%UZbZ:2Anڼvݚt[W%CajAP,Kʻv8 iUK"E #h.gkgr7K_9!s6aܘ 5$i/:aZ|qz*Q颱90#jm[.bv$SRN׫=zSJe@,axE[̔W4{A(VTXQ}nwGp$ld9?<41D mBR1dxI"'bc][`xB/2ǟ}H}F* BP( ZS% 2YkhIlxkӿ|ћ7nm4x "ssGO3֍[:WaZ^;u`)tfCϜ:sRT瑔$IA"FƸcf8$H8^fi5lyc$6L4G+Y@u*AZZ%źek֮8|\z|*besld X̾zLBgƞ0nd4 >]e=>CLϗH*[V -܁wdBvDuFm֌5}n2X&Sli0i /#C/t-۱d-kZ,Cg5S#+W/a}5 $},t @E¡?W_޽o֮. R2- xޛ ̓{,4ZoY'8sLY ~6 C{4E 4usV&2N%7&}=731pIG=8%mI?>4323+ɴ?c ٹt]O1B:&6Nϧg @5z̢ s(θA#K DiejST RJ PI=Z;Ŋ.ckD$ѕ9Ts'xc`ב/Z??Bky?$HBP( BP\s.5m>sQ(tUw_r3'څhɈ$@4s5arm_ڱV.{@VR{v߱~wqy(ι.9 djB V@xXsA 2/T_^ m̾{<Xjr/z#}& UW`A,y1!F $I_tsDF oYӷόy!аh&Z՗k׭ ֭&* fзoɲG͜jY IrXИĊ!/X?=yxB:c[yD8 Kēf(6D1M3 nX4ƈlg3Lz6ɖb!KRH W>zzѓw|6-YyE4nbR"Y%tIs%+(HXXzѲr"ZUe2bHW86lOgJ- =tbhLi븅\itnI* J5'Pg# wXދpSwhv'Y_,q"AJ$@T'@e@'R#^>V4ti=aq$5+flw56\qrP7R/RXP4:`$E=W3LVmF*%~򊼐RJR"~5>OBP( BP(y4@.|Χ^:OutKkא2 9eX,5|eۭ7O<6޶ Gd—~Y;w:955$>snM!i"k(\LykXmn^d<􋏿8tt*k$DȍpoņM[[Z 3lz(Ƹq]PJb۶4ى3Ãr~+ۗzLMnjMw{VlXEDt}#$Gk6oxW#;~+6l9"$S1{j7yS`509;t‰s5?[n_ϒ_@oݵٶ>g4[;Bw^Fcipv G(|Xal "D@D5 \J$g^鋱CW& 3#i)DPIiiZw-M]{^oFXHw$VKGP&ySƠhUN$ ̷%0ȐϹVwYCyfr}S{ |nPT( BP( uB[cZ{yn|2-(Q<-;#c%:D4l!cM0]zǞ}敽-h=:8q2 oY;0a` Y!^6B%R $hcS{=s@~nN@ [:noJ46'uC(?RTY2Pߴ*I!g'ǦFN;<3;s L&5752ylmwtCw,Yg7X׆@$m@Mo\6`LtEd3bo߻02@Ɇp.Ȝ<O2'$| Ɵ-rx~p>fKa}sO9\ۦ֏̍e`!CgrTrs'FYiN9]Ûk4aMTYXuQ>$Kn|ױTL!I4KqjhZmӊ/|W+q^ BP( BP\[^v} +۶ID1S, :z覞eN%hWl(`z4*7ާ9(m;7SH?982;G޷ԞELӮ}MWZ}sG"@HݼʷOHOyR0D 'n[t.$ 2h"ٗYvA04#8E{V]ix^14x@0Nݹ?ֻrg,!%\g83!kkkmMPFF׭k0C5#&oo.jKMDManf˹︙TpE w0cT}4t2x[Z21QQ<6ڰcS"mr,t&5B =\:ωR]@LEe$UYz3ɗ QH!0T^ <޹$S& Yqb70"TsqnSGJB["1p1EoF'@j`_SCqFlk|> QP( BP(:0,_?']w;Wm]Hׅ\^oƣ;Ǟx9l2 i︳}YC$bL|E+PFDO~ǿ!Tu@nD[;le{ƛZ43Nn~+ArEh XqimP];>=ԟ/b9{=\Sn}֞&-~^jrɏbo'w,*.7h4d nĨ{fJ.J+ .Dt: ӒX"c*sKLfO"!;4uTѱ&G|-z|ywrY#ڔcH2:ƶ9n u-OteE.D~;(}D!IyW&ͧ_Ӆ+cX$pTKA2Jo_9wM2 ?S=-PP( BP(j%=a1ny]G[lhښeI"Ir95bAaM6uˉz RKM%R=/O:Y+O,h^l=gdgj0pĚ A:t\*: Ag@$"$PovEt.]ٽ'ٝLy$Bɵ¿&'?^ՅZPx]J'eCoOk?<1=|)Y WhpBQ԰ TvHEmV0]:rEt]i{y|ؘ.9r}5&K|N3( wޱ~Z]=wmwo1"w;B>hogNj3{ȉoќ B 5! "9";ƒˑBi ysy&ҟ+( yEX){϶TtY(Jԑ!ߵ9t,GF P2:2@z=vjxQ:ePB`@ ޢuEǸ`.Rʳu%dz 3VF&*txyo,x) 6?,w+d(]_ca)P( BP( Pͯ%T(Vd.*\Ahgg'>ç)SfZv-@}q:5#MnX`J!(TY uJDPtK^o@XwniYƒgT|kr.9|dH>2H|)/}4DPna`ZGGOSck[G7%Z][&Z^x[I-v47޷-auNZ`߇tubspWKscs蚦qΐ?D{QB3䦊WN٠s" ?76)s8mGJ;ru$(=4TrɁq@˒wfQDnx;SJ<*:$"ͳX[ {Be[ ~%#{~+D5+2]]~ $]@Xqk^M6Su@xt\/pۦ#PP( BP(5%-E_ L{;{YVoF.A$bG]k~&-ᦖD4b2\hr~z?ʕ9Ϻң 1.ȫ|Ko{޼~mѦfXP0K&7"D ڳk23tY&PCMJ!C\KsNn6o9Se&tػe=֮ݺZ $8g$ HHZ(w[z񉙙3_r_ 꼷߽-w1Q-bo p/>ܫ{۶'V眇&cཷo+dSs %%6233sS91$@-%{Ą)%Wt޺vݛu jQgЬx>Nt!d' rҏ:FRH"!T,UT+ V߾QP+F@hDY~ȒeyMq˪BP( BP(^KXKܶko*/FO'zz|ɠғբzH\O# |σk tuNT4DԤqC'w䥣r4=K.߸5kDY= :'M0u{E1G8'P5ir(Zf6Rd͜8oāmo_mh V`m]XxǓ}*^}U1j*nj g!O䑌}ypwtjSilZj)2x"i #g>C6̦Ds=5F.dk"p wlN,? |:+sˣi(ٌ4M&s5=7c9wXy#V4'Ǧ-­ ői,8Zμ% P1bc-<3Xg Y@ &Gonʠ"{kz~@D?g6qBqP( BP(lҥ IMɡĻ>%hֱ"0 5$ƷAAn.[xf$_xvuy.vah1nI3`eҳs}[7l[ &($T.9zW t5bAhwvapDqefJki1]F3w: KeQޘ`i?_5TnșgCJ1s8#sui.WzƧ}FjŊ ۰vD"XLoi+;Ԉ Ϊ\J;Gχc?y5Mf~U˻?O>BP( BPhF!2W ^y753SZ{a-lh#"aU>+uoȈS3sX#ͷkipA=*"ΡWtn+bzT.kVp3|xT5HB&A4e02\8x؟ۺ%MH$R,K kioO/3ىԳ=dEσ}(]t9%!Mwo]O"F)ds_O[gs"3^,Yd-O>6jxS >yK#0dGNOYq扂ZWkQ3-'|͍0됇œnw mkIUÚM)TnB1]HMyٍAHRLn25 _yqR"f%_~¿͎$+fTr`">嗞%˅vs&{IՔ S~6Y/B֜ 8:+D3 HRK9GN:JoXKzFDA< h{SR<17ƗYfՆ[ifpξ$] ˔Aha7yLf802=>r\HB<5-q&}oʅICIM˰tNN G/`]Xci7_E|b1eh{S=9ټ]˛Q,OyS%DkcT :o_OrԮm))$(1ih+{I\k+^\سbt^ %2鳗}mr t7ȇ;VrVI%B`4 R\dh|_ eúd(p$3,4SC3pdǁ]ۻnzn-kK)Ab6tRG*w3_B[q u hYU h]n$DQ5@qU?vpjl*=W#m?|:%l pJ`[‰kiϋ%ub'nXag* H `|=5-K%^dXx1/Pg!>8u'6_ P!^x\Sx҆*)46*%-;bwiHD.~pR^ ~T}) BP( B[]Zt/վ^?Rn0}+?WL &$HDdB6zξH<1ݫ$$6r|aRu7>ڠ& V:Hdg㣪9i^7E((ֵm]W]]u꺫w6DC L213$P&@D}wn^XrU0|V![dGVs$fɟ4TQ͍zDfDB0xzB;WW i>QgjN0CP#0(K Chұ( Ir,RC2n&tc\#"E mh"<Uw,Y5vnw^ $pQj))mr544v5C󊪆cyfؓ,i)>Y} d!bb28kڱkt{+/m]Cs0y]!53ҜIٙ9Y)yiʒcĸ}gUeMcjW64=h,U5 r3/MRVFJ^vZZ=5^Qѷ0;1 $jz\S.kRUIt໫bz~NOuج本\Qާ 7F7!" 10P4G^n: .~v(D<'@夔Ayi$J2Xe$d?n[Q HQ3/!Pf69p *"0 DY6_slŊ.4t4$:#"|tKԦ ɐHO3 7yZT%d1YKːǍEٽm%rrӜ4gSE+ l}._WUWsp6^z$d8΍nK3&e(:HN/ʑ f*PIO_P7ׇ󉣦 %eJm ˚'vD!39,Ys)`c#yh.7Z˶ʐEUzR{ރV+u 3ۑy 1ƘpHDmsg^!dZ;.M|%ЊfӯPmGeNQϘ2vҸ!9m|yKWoiYMiǏ8cQ+z}[ܴ *3E')Lw&"-C:Wl(VZ}g>h??[^Y/w@EF>(1^_pvl.غrf??[VՉl/o`10 7=1 zV߲kڒMKʪ~8~yC ?jh1 蛛8!);g1ƽ !7 X,o}C3: dxP&j(u=̶y?? Mʻ}~W `29Og0,:i I) zg-\IGa} =%3ŚvH$bQ92h N;iP62 ^5;V . إ ۽of?0בE1 ))Ix=s4 9O.ݺA 芾x13rf~;ڑf,[8oNt{l!'%e;wniГOF)LbqkzH%uZC;K;Ural0yZ1(  cHYXv")sz΂jCGCd. ac ;MHBm ^_х~ ^зYӧ:n蠬$ ĉvpBqE.Z+H*2=-M2\jv'} &q%b1 Bd*v46)l6qKJ醿#&wDZڼcgv"f&gf,ѧ0 uՔ" a֤X3ysEQHw;[?j6V1Q>3Z7r-ڬķ1`H䦿}`_=ݬ8nhbĭ=يUc/6n볾p!F8%+.Y'9UO)9)Lk(+dVv/Y7? LƌES/ĒdlزOقzwUm\IN2檋O=Jz>8yֽُ U5>g}[/:ī.z'z@]Hb(0b\7($ȩY'83πe_]x3#JH[,3u=ųmZA(E_Ww$ˢ(rM2"lIbZ5#+f=w_{mA]EQ0;P9Nl0WE HdJ*X%]b: ej`3aLzLIKdeifI@kOJH;kfpKE L>/dnj:Qku=@#]NB b?*|?By@?9w|AG_~X]wun1'? ~?;R ]7y+s2S~}97]5#-ŞX#Ҫ?!Rn7{+~7fDop'Xzkw>ף#Ose~K'Į;:4P:"0 D"U9d'ך}>A4 D11$nQ P+A{!6HA!آ.njCӐMvls0Q"Dd2 V(#[HɰٸG! K]aH+85938o ]eod,1AOˢE+Y'_OH ʲ"PUOYPGvOK[zWoߏ,p4˶wzCd8sxfCտyG߸sorޫd\5Yҍ[wTjEiiqa]g+ 2NS<v?>/~^ʼ >/=_Ldg$´CƈtP@bʪ`JS8q!j!lIw$"Ù7#il!h̵C %ry}I7 9Eͷ;S̢~h>.[k|4$̍ ڲE| aS#9DÆ(6[LK'# >iI+[:lVOZU]S?(  D&v%UEOXZVrUl[YYμ~9f7DDVƸ%r8M Jb8p@PCZM!IU^mVX_UgԷfULIsӧєlWA#5DGi vU,yj-AR 0v 쒺hQ, ](ȉ5Ӟ6$,,γRyA=ɐ(o}3aDj֚|ٺ%}(! DQecB$P#0h=wEhȰhM$vHsoƃxr$J.]5W^tJFZ(XǒU[Ͽ_a_>읿hYǵqkʼ Tjϯ=ܧ[9KJ%C곯>ijA7ݵN{қÆv<.\kFTYﯯ?ߏﶋs{ۆ?z_|#; V* m 2ix@Q^_g?~/ PT׿z9w|63Q67.7wcƒH@Ȑ:lR8L`D$&eQ$HEbdp0W#3@ Uծr0td4U sQ ЍKT6jI)WMM"0V`;KeWm#D\( 5đlc%@BqVj,2$!+OONʮm&/qfi%Iqd Eq͒F/;ؕcmf@:`83.?.ph]qTrz_YZu2^|¤~Յ3g; #*ɩfAVV.y;HjN0 %tD,rL%. ((Pc" 3DlE5kVoY)F9An2d 5y'g h-s2UQJ+FĐ3A_Dss_w_{VN4U$€7x.ʚ[L`4h@5wOdpOJt 4J9MK!d,Ȃf7mTYRI ŎybEiD~#6~ %.v-A-wgCJK)gH= A!AqK,c$#3 D4DH!C@F(J0   9gC%\gpC7t]3jv[p)"Fh@^cceyv(ٶqgB.)TK wΆƖޟſ^,(旈^|!'ʻ{ö\s/{@aẑ; r#~x o}<{=~ui˟{7PYvK+\Rql?/ZT{oG{|w=}%IHػÈ()MD]u"'fZv& xVf I` 芃s1/| "(WVWjh0xL!dD=hAA 9#[)fɭ&`9 "hGQغUzu1\F `%2)+j7cfKm;0tD$LMjQ_Crmd Imr5Wש"Y $^:mt@RrR C 3׮ڸaݶJWs0&IBEy?_qlSrRsS1TH̲YFv7v5A_!xH7V((iAkfj(:) lɭ ;1Q0ԄA}ckkl ,7I3@NNʛ4ۢ'C&/BHպuk=" A``ĉGtQؙX*Glߌ<+E9KQ2$F8rpn)He~H>yw]aG]vz.Pո?牔7-_SO 驎2]n?Gi 7?iou\}fy%#PSO|w壎hz԰~0.|풻yu y.}ꙧ>/{꘾k*ה\qU ¢,\iw_qyjѫ(?3ɽ0ox X[2v96kY\jzˏܛُw/G^֎Z@ _^z]nVbMlgr9<*H@B&`6.`I1ˊz+K nTiC:&|Ϟi8O! 1/vw5 } d`C-=;%9&BP 駟~CB4f&q ! C$mT_'㘾Fi(`K-9iR  32C uqV1"ɲiUa#Xs0,9qgfdfĘ|8tbբU 4b-%=)" ]%@PT= s PsWj9IH# .`j"ID!Cuy+J OK%up3\"-EUM(un#D,o>'ڱxmKWif.秈1ZAYAHC "@ HD1_B"ܠ;;vfMiyNIĩUr55bBzr;̹V-H8RKǖj*-a" #)-u F\a N o f]k+(E8QlOj޼+PU'avre'Xe'pYMIbJsX_`Y8:&Щ%JsNx~cǀeL&Q;˪+^O)?bcm΁l춆i?e*/b8z橉˜vd-NfK3?x,vY'uIA_z57ބOY<;di|y>JqUvݝ7Od+[z6&+ڪƞ+M7~q,Fk3c?\"*|z@$y(m!#"Djǔ4"M:z ne%Z\⡣ɲd!=Òd Q{a"QUu]g0YLM#QUi""2DROU f΍e%{PaˏkB^R &HJLHeM:fdfL\ۢ0! 6 :xHMV{n[Ll7%HvMܟ(a oJ(7z`+0&Oqb&jOȯ;ᅷr} xNy91G!{s-%Loo̸wb6ה=w?]_v_;~1=e嵉걳ƕ>WK9=^f OvwM|/vJtC] AذzeA&Ӛn0h5_'`<%$HU¾-VnRBAٜcƞ \\.1ց- PdX-KD !yZfc 6Ty<6\t W ^.iQ,]fMfsKW_S,6њ$Y"ƀ7Vd@[N~DuREG#!+Tp%ȫ+=-^$&pdN ϻ@mas`G4ެvm+C-侒UТ-APt6;ζǾvQG.OWܣJ@e]MM 5rmzgAѫ5zVlmڸKԸ$njЎhZq1"(+v sm)x0M&bOkl3'%oo:?ث75Ͽu0-ٔ#Tʤa;~_H[lInÖ]8ZPYr}o|071{E'v$bxNy_v=%֨gΆa~d"&|2+CkqO܃xEAW?]ÚGiZEË]4 ),NdDIT{: .doKwWuٹE$$L}ғS̢X;[bR|WBAf5D ì 2@F  >0jO0 0da,_dsT8D O[vmvC@AH$$놢d2f.~}dD@2J7m/ݰS 3Xmq{td{ßL4Rʺ6]npĐ3f!"RbSPA .jY[Uז=kV1N+z;@NVZH]F  [MARUPMw{}ku%ݸKQe YbE[뚆ũu!bMdyl@<Mt.5/[v1vBώ:"S*l4 À39FrȮ*/M}˅G:PQ021=Wlpk6&kTָ^|f~, wXS'()p]+m}KY 3=Ȝwz哯*ȎXl=>VTK!2`kj_(:̒ddD)Wn$v4I0bֳuA2rDbMKYL$ *{Kp\DD,RCB 3<MڧejW_xcW?}{l€OvᚏoܭZ[>BL&M-V3Śa8 6<>6q:~ŝmߞ#AQ M-J@65%D3$I`P1##=i$pN*N:MZS7V)-e:B+Ή "#2(( "v(0wEveJuEצDVbŚp C w2ӎE=8e~\a骭xO Cyqi>9)E7< 7>{e&2rm$;DՏ%bP]4{`~yiMcf7~)Vq%44 $A54ttŮ <8s0 uyIJ`۫ۦ+*G15=܄I$يIYY   H%`dx -Y)d;e-9 ۈ _e׻sWȄcRD"2a/nIJJ$Ѭr qi) @c #"pNs"08:Tَ*vγ dњCu44  RQ9R".r@"08k+Dx94qx בȐaoy"Ĵn vǺLjAwY0+mr$ұÊ;cB/?g==9=q6r-[jvɃ[w>LOuf9 rX:(U #Suw tՌD@<>VOIÖIOOuج+=&Oe|X~,ћPj>?Tv{[3ғ2VKXRRWM]=^srɣ؜i1O4_ra>hAnzq촌4aN[lVsM]@Ccs T65V5YKMϾnacVPUQO~nh)3C$C&\Qm)%9Bf"lG1 seo-6U+l]qATKjUp] .ℂa#"kZVtfjE(Z*AeҕFiLKNOr:!2 "=M;~'KUd ) `ȁ"d"@fEѺ@TQU l^um7J G[!N$vUM7 %dBf_1Xe{KC $OwNUCbfBv^ƧU@1H@B@#ppbJڰ5^ KM]>El"jQIʎ1%( U1!5ڟ1.684K#>ܯ^ڇ$Xz[->eLz+N/z[ˇ.JLf|]5񴐓rŅS &7x~/oW~Ӷ [v߲sm&stA"{O>9tqwPA= 撊e%eU+n_We}}^_gD!K3ÎՇйY|hdnOOOei']'"Q Ѭv/b>![\9DbD`Κ8~$A cMM;n-)"ڟƈ1DLHM7>oAF3--EpMA CЌf?*90MPt&0Dv`0$PY]⒵AaSӕ*m6!Ksd-J;VfNYJOoB Hx1Úh 4L$p vs#1Т*])\ 1F-{ZɢmZ8vi Kf}?;wO /J_.۶ ;szRA.!8 B>ז|tI`(L8kO:hdm/m!0vZZ8%?j̲D )L@2tF )r@e3mfƄb. qh ` iYQ@qi5!H>@ Q@99Iv7}>/{o&&z1^!L:lV5bо\5 O'&$^u:WkyهH9tv'$a SW^t53O3]vS`?C]\s?}cFj׌+p)HI9.;})c${-}˅zù}Pב|y$ ^|2CO1υgN)vo^o0kw6nZUY592&89b r5 We (J?(-3*¹` "4b0 G .4>ENj=S;dh6q4"hv',H&LAD5Cik^-oX =al "0 3RG@2Q6\u#`b;Qu(C 7ڳi{JR^|UAR`aO޹ݠ9R6$,<<$UW$Q" *!E 8a@H .<1_ `u5W0'Z  ݖf IT.mb~UTڍg] TYD9Elo˦jY;~c34dDUȈl }cE"$A:~H˦lݻy^"<,eçb"i/ *2~R ᎝>qB[o/LIL&Y9K;ot`y鿽¼#8Cz5>ߵ׿Jq&=+d:|-w#QLsV'lزLM?k~yG6԰~?}_Oևs _ Lw(7qݲ/?[bw WtL¢gL+3JQSejv޼uߑ!M8iϓd16'{ip ɚLHeu 5ڙ7cXTPdX(T`IIb DB q)}E"~#9D ,gLךaPXxI:!J9)XLTWՠ]Kƞ:VQQDƄb·"Egrʶ-~5`q))VOIdYи!UAQ4]'C5~j3AH5y.^R}.$l)L^$ٔ$I KJ%Yֿ~muABf4r$TOu N6 dlG NM"p{t)@ (2{b6E3یjF Dd"F0ȻA NLQtpps[J<0vp41F'?~.1_kY)xxdE T#7~r0vy']zϾ]ow{e@P藏εK ߙ'9t+=81đSOOٷKQ^{Nzjx}zUNZL;n/wkсdΛ I5imVl޲}}k75{~y<8Rm$dL;F2D>/;蠟|sFZO!s_x{~u]<{mﯯ]+:lRZ(ɂ9+=и>HvUTnURzv!DȣU0yPfڐBw^v9Œ`d{q_3I@@Ʌj#))5)& #cjnh#Oyةc2d_Kju+k}JIb@nfQ" P %sݞiQTB*4OK9q^.8k!JuvStUY=p`s&7{V{(}_sO?zxrVo' 5߱vݥBt WN7˷|;|ݍ-Om<͸ݽ~W4ޥW9& kz Pyeْf% iY9'O3lLNfd;Prceq<vj9TM Dedde3s!={v( $C2j74n-J(]@̚>y :$vn:a+C*3R& ̗%Q$yDA%XU %_~rf}Fv直yǟvBRax :&wYb -jF[ȓ|7=o9oInܫ/9z^/?*LYwNf˷9lL fLOseceM5;Օm7EwW{伾) "0~:֌ZBz@U8qDNyl dgf$~6ɋ,B0d0jK=$"4[lpa)1)O,m;C&;S>y7_Wו ?5'M`v\Dj˺HǾ )3-zDUlPs Z*2P[UjvpO} RxȣlZ|vo ?!"0@q9C`Vns+ۛȥwxડT7haŧK}S6TTUJjnzz\9M;o)2푅Q@d e BϰS:$!$&18Z u7%*P@q7[i5,;<)]huwƕK`?8k_cy!7vY oLv^{QQ(3γ~Co|ىa?GE?}3蒵^zIySagp3Jjsպ/6+W1|_.VDfa?+qҩpds @$A([tq}Rșȑ!2=e9nYWq&,LfGl/3ÇK6;]w4mb4\a&`BqO6GF2h_6fp Z/ 6V&^p=2훷o۰a&9mЕTҐ&PP?UVCjH&rH4n*b@W.)r)O {-Y򇹵;7,"5QC 87PӉ:ߒo5⛍>\ZSh1geͪ*R]kdQ$b@oydDقA!I9L@Q@A"WjaE3wxz?~nlp~+3O=VOX5?OۍmY!P%7L/>}z8^xcvF7ۅUָ{e,mZ~=v c̝748n9[rG)>bai`h࿺>[w=,}s;uuet55~꽧5k-!q` RZ@g SN8aEO2q3fyzvҌKN2 S@u d,Bu@]˛S!E1C.uk*s"$;m$Й\ȓN>(U@kUp3PIp)O#ԉ^[3IOls`Q؏u =5;bԐ :,Q5WMO:hP$Kk@}Iۂ$aRj+|>" UC 0?!_սv͜˾tgM={7e S:Eu.q"а.Qx[-]tCPAVBQ#ڼeC]7[m;/)ٙ$0(-9i' &D Ȅf %K$F AQvAJnue{w hxw[9Ol0ˉ)#dpt  8/'X}+׋~lN =q6r-tcW<Ϟ8JY3R|둇ﺢ5k~^8RѼ\ؽmf'#?t}81+)cٟozُz_,#R[/d3c>rg=ޖWRķVق$ EA1?n,/64ElJ_@aE[o N~PW.9L=04_aK"(I>W`REyU;%+ʛȒd6uSǎ2.(EIcb1x }uOr7CA-&s}BG+vT謓RLB A(KxJɋokmH{QboEs)'Iiv qJ^#gdq_E7l[钅?1+|\=N{Y0L 90xX7UW[d[ ! Sk.pbIP ٷsƒkʶVW\uᅪ0aKvTVֺZT=.:c:>rT:.D: B4tx!@ Aq54UHh!3:\NT-bJ8Wom$Mb C9@N]peӎqڪf^~fۍ7ꢣzw_1fDk˺W_@,vVH3|KŒ_fuO:1FMt_\Rэ :w 5Ξ6nw䦿X[ҍ_6y czU,%5Bw^v⸡G -f\8Q2R/<=٩ac,#=bԹۛ*k~񧍲 NISCI\.Z%dTU77j2cMIvIV.Rءb/vc؀}r5 khyOW78qUQlb(Bdi'Ndݛʽ%EL#,]Dօ:.8%XTd`;5EJ:̅Ybx\Z5_ANLHE5Uߺ~SuM)gO-$D1#5DDd02G#r=AČ,ˠiGYlKem4j"0EL'[n}eɶۛ+@sM;vV[{业<7L7jE'?WH2%Y~{}tWjzWyGv`x=Ux/ģbDZyELI.;J q\HثM1t!#E0aԚQ<{At7w,]VLq5 .ѣGC9EGcb!,)M#6Nle:t j8GSl2Ry\!t8\>ݫ6hd[?+7l^X0ȍd 7#~g{ںU?quN!U)ۇ&5HbҍjD~Ds#FQ AG&!wןvȁ u?VmK#6S22Ygj19Vq(3Gx49 Uj.I֐YY8"E.H3qg?l޵rJb\5tR 4I)L2O;nE IE De&RR҇j[}zO]wݮ%^P34!g P"S9LQr,I62myWzlZck}CYU#C ha9 TLi.tg^F,-z)ʣbah#)V~[kX#~?uv/cFc"t9r/YdM\d˸#GE4|0/B&p0W6wr=~5NXc!z8[g$;l/uŢ(ܽ OA<ۃ0|ڿj  wx^ /- k.9['쵇zJ{~lϯvuG?\yS'J܎ęjB-,T4"3t9H/N-*?'O=`xaŰ@ZJ?@`"%dIJL?Kho߫6(8 e#BM_Tf./I+MYMUy'>bЂAYNQS# QybStDQŞ}S5@ ~oК[ --ʮ+]چ&_Ճ:9piHH #ňj!V;|[rDijhٶb]Jv}@jF{ƌqӧs2YntV[b!3ӝ@t =4?:vXȧ_0x<-\r.]S3#"^q޽)) z_-nn۹ ?2[[7sPT?]Nɫ;x@o [^V-M1ࣗ8֤|7bz}c#x ꟗ8x齲TnVY}9.z:{}Q@bg 9 IsΘe'A%'$9Ҳ &pJpt`Ls@ rMOQ)#&Jتc؟ʊb+Ad2~u;qYa(bP4[.YueI٦7NȞ:Yͭk#" @L)i)#KAk-f)3,#It@A[76~8oxeɓϜy^f^& 7 `ѼEoi>񜓓 %%!3ɉaD5nTrȽ$ (3?Udӷ>ݺ~ ԓL|לs(Jj?}؝"1"pQ$P&< &Af3H ]硠 *>jo66y=->7Xk7mu?80jKoV'[kj*Ȑ$[ίYZ[k4Qjc՝IHĈPeA#Rb;vֺ<>HNN8q 7p`^dpBأ` 2%9)qœis*?ǟp©GPs| ;{a`o='c_~2pHWlbg_8~lw S _Oz(s5O~E_ȱ54?7}k%Nt5q8az ⍿~5w,^ygE]2=qgL{ͥ9!^hYbsuM6Y#)V C7HioTs-;,\y(e.j,f0[2n QI0 N_[Ss?˾srXpfvyW\~iKܴsC2' gv+cY{R DE!,&' s Na:2a[g[m1-ٰùl[ͤ#laM&+]ʾڙ\7ɲ0d*iI鞟FDb0pI{Y)'s GCi͍Ҫǎt)OCKЫ66z*6/]LRFfjaQ3Ŗf&cL`3&rV#!mG&f)ĉ몮CA-P`(W}ߧ׻ )F@T] ]  ( !&;8H I`$v);=o^V-++5_jA@䑺FrB.:n^TCcCQ""Uw^b}iy]uuLrɣƎiZ$p][ 2xpǍ/|׾a-F Ӌg)Ͻ >kx%;l^Y0jXy]<ߋ) znijҸ!qiޫ|z9~EG _ {> o^>1vĀ}C/|ϝ^X-owJYOZ{2FnV~[j?+nV 4?{?wEc߱t}.O1d`H;3s%Nwq%;l9cTK1흾@IΝܥKXqǰᴙޠ.7xu3Îw$ki$zͲRԖ$%UV6f1,Z_oE ::,еJ#BZfk/hijy8‘ꈖu}} 9s/bMѾ%kSIfĈawT]=VA)0ўbjꧾMr9SSRЙ4*71q䋦:R@@9KK)"!q Sxq$5`㦎7IsVTԻ+ƍT[ȈYeaA}(RU=j}J}vi79S6d6fgɶ9dA&1IUH)2JQL&9st8r ]9 5s2 udp9NQuQMf3c IleY`LuN]i9˺%g9I 1!`"ej5VҡdM{i츿#uCո+l*YWVCn;"{̸8$7(dbȉa0ƀ֖c6^$dBL oo7)W?ޫ:^=q6rG ~!5:퐖b{f}?{HE̘pUgAu]Sd}0u SwnغtWMM{n(?ۚn]7_2O^y`o^h8{ڸĬJCq5n{w_s<(NTQӴb>Жl $.`9! 笆Du܈"FY2I!U*dywYPn01$ NKڵ{ cH00.ɇKjER{2EYS\x W^峯ٽx`h8glDD"H;W+c`|ik%0)w[Ywu+.dBWE/r 3OyZvq3 K0&@:u]`Cd2cGˈ)3׭[l}ɺme6!RVVGSlD4X74~_omuWnY4ӒfOKONIBdXK FIv6ŽbԸH@ QI1;&:kQSpIPUU鴎Q{wmMMCye͐~mFlEl =31\5lKut$ ku ZtCL?h#-*藖f5[E }yD܉YSOÎBRL .d\Ͽy7'?;~ʭ;vB^v|Y&~!dFoHlaE|>r#Ի|S/~-n*6s#SwM8 DrNb[w3zxD1rh߿?poRM= BQx>C r?;oҪ_|f?|9jhdJ(j& "Hc="D1H4 ] PB$[|ڐ`Cr9)66p BF'}Koz^^)'ڒI<, )˂Ӓ%kw{|W `J='SbrA#C!V޸o\+eN=ZVl,HgzW0˱jp@O&8 &b*s}Fff9  0C lS>pd֯TWq{yPE` Ѓ8#2Z !:8j((nx+._SwǶ+!̔[Q gnaNzfr$$#Iٝ:oGiV))=l#Ӯ02d -ܼ'nͥ;S s2a ~;]STiSCQq$CQ'tM;3O;0=O=,X &{.XHݖ=^ʻ㑍Ѽ"7+MNc1w|ٷK+닎mz&Yzyug=wE"m*pF'rLm&Ob{y6 eg']zѩ?,S6XUM*l|~EDPCNG٬EE$ebᢇD 0$Ni,ˡ""7y~A4Et #$ c̈ͧWcNE^esF] <.׺%`<~$ Hf cpK%n7H j4A 4}`Sfuܚdrٝ)LGFvJZb3IfQ…3 0ƺH<#c(E$FrX5CPԴ4#ĭrq_Y@ 9DŮ?1:`@*-^tWUeKu{syu,"gQNbSSe $IA7{;C ի)4'M8S8 ,9=?w>ɗymI6VɨXL~´9*1iVM0L1JN:kElr8HffIY%K&fn0] Id:iX0D*sUW7{=aWÚ 7]}FRʏ2~7U~SSGON;n]5׭olaѩ*q;9_@gR;%`Bp-ԏUvu zk9G(~ꅜg5yˊ'C?;ޱ~ qIgOj g'pqujB~sZ;7o?Y_O ڷo~3s_|1RB<=>T{#8Bv~?<:z}k;-n#J)|6粙@HTLŒ*ˀ%0BNuNe/#N%qfY/2Ba(B2j+^7`\Hr/1xݑsZƔmOyV@#EBBɼwKcO}#[vmTݫ~Ǐ ?ylhxԢp5U5`g1L Z4KT>f%ǿqk}< h۲':w?~p԰W*/WY W*N iAVdlb{dGi1ΞƇnk h#X#zntEQ&%eG-5cćN7x`!^nmj 667xn-p󧻩!悌IҼr Fl;83˶eD<]L9ϦS3Rqsi>= 55GZ}MqID)mXaN)0@ Q@WWˆ kuˢ5*ۧǟ}s*.XH2ڶӿ?fG[u޷jj [D s%QfBXGˤuEі,Kt. % 1dL_d UyK i!8eP  2HS*cTҫp*bBͥDA˙t֙}#d:R ep{{/r۽~m|>*U,v4Gw޴kq6vצF ǧrÜ 1@: erxTu75m˦Www 5DIX.˭m8C C|ghniF+}X*⇕  ! A̔EW(/(掀mjUK& A]a|QW%0? G'::/[4 P:,ch*RGO/Kmz[^+5zꒈ"gg b$"I~qGqqaXBT4s9=*b\:HMΟ<4G^vGHVeU$""M߸f@], ODBhD1VX<)ZW+mK42I d CCtTf Iwl0dhS:-]`xEޭ6ْ$ֲi;8oDSz[OKF-nܳi0#s}qc:W|'|dY7k 򝇟eB/=xA>[>^kCYm詅<|b{vg=  X ,~Wgo%czhq;nӵnukocˠ2b H,ĦH 9`il0F9(pWC#ƹ,6iq¿d[O G0$,WI !8w={Gڻ?~8 6@D$0j-kZWe+K^̕j纮d[c,ɪ4*kDAWo9cfd{#%g{ںMvI'Lbrl\NX_S"1B !ZJ8B0QkEA~׆G'!*-;Wrdi#+*2T:]*!ףu4 3 lDl 8{6&˵|~=|a[^4K}>[y0-+XW?}Ec5 t{ꋿKlg|f a;j_qB6<\=²$E!z:~j D}kkGCR{N ̃AW?|˯$& 䮛6UR C9gaqıêB0!1 qXȑ aɥK;bpʱneGĤI#B@6_% Oo}s۷!,* A B(C#P38DŽTT@#xy\ B.r%?yj+G~ekco3VH|!?%hPgs9 *AwsN䓰NbD6\sm]BaQ@Lt|6ӄNkn[`;-Z2BA_J9=/,.eBY˱xvl( HM3m-M I!#DɜX#"!I_ 5F9uiPhKrR&_-%3N9/GsinyZ! &!`e 7:ztptlz՘ ʪ4?u WK"#K0OG2ONsRlq664|>v6ʦ93 !9T! hŊB3:`~f?.ؤףs<-ԯguG|sB"7_Z"l\yu.Pїkۿ~g}q05xV6%;o᧵ԟ.&2;~2qۚEN>Ol- >܇|/ݛj>G￾.( +\MxKBՇ'd&pMB J3U+1*EUΌ j93+(f4,>#pވag z3+ 0n9P\u]hǞ[6TUT-Kbe1D$*fU"眢B0rr䉹i+n JPFZ~!sd ` ]VHO +UEB#l ntbT饄˗3)=rb$+vl?z|txd^YQ0GR`39p^+)!0l6 ;e R2Kl1+d8%4H8ͭP*" ́Ti\FWp)p ne!Ne9F S/YżRf&c''uPUMsAO[gcWOk \n Ǣ(hlu7''(`L#qdVtl}JKWPb fbǎ LN.d%Ӵ󺽭--MQf. L8;U[A#q; "Qk.{jx5 T;k}{/h|P-7wܴTsymMKkꅐ#ܹFEǞ9p}b:68:[]g?qW}stFە? >Bǰylr]u=\<&,sk8/C9?_L.8m h$Q21(/syA%qoP#2%KmCDW cR /[ ;q-WT*6g@!{[ZF~_}Gmضkw!L"H +MMB2+wpeۚhKX v4eHj^!xޤ-Y1aဉ U5M& @8ccۦIms Ih]W߲mUKKW UJ~Ϟ~-ɉx4Y֢ϥu5(JcfY2Y=Islq)l0f=%y T P[[W8kio =KuI dU-vrBa *!խFE&˹L))leKgb^)@cS8hno.%sB٩@ \=[6U*ş0T2b#c3cSN&Wz"iʲCrz {_ʥSp1i `btB3CoxwMveLޑ]y>`-,$B_yB>Z"6?|uasɡZ"|7:.VcwVR3Fg?jR?}zjFx2ד^=?Пz>K,)?Xh5fڵ/8ɅLz) ;BhWADQJX"Y孲=68.x2fYHY!DIDѣ)^M zMPc 64PL2t\(Jf`$ӹl:_JsB+LִL&ˢ(*DsaoKK)nmo݊"TIYղ#T)dUUMm~F9aŢQT!+D:YJgn*%"i_|.k[&_.~(7yp;տ2@R\.#Gfg㉥tT21U}@$P&xΙpL<"TF'O.It`h1 #Mhs(a,́#~Re &2_8-;z9G8?naq5*U.+j#n/:. za߉Z"c<?{V^yCcKCK{*K%"J(T橩T8ղMq۰ llnaj|zi)562:9680R}}WlhiDEMAa +2wjwq^)C|a>}~U0Ba,D`q,z^C@@o芄,K$54FoM[Ϫ`EtB!Q L\.um߮58L./O,b/ qF$ˊ4- h 4vab%(VI7ϕb|)b._2mfEAE&{<#H [[#ᰧ5r{dYj$ʗ 99WmMND,*//cQǢRhe3R/|zjv.Den9(/0g.As1 STa``|`p|fv1H[B@$ z`󩪨J~_dđn0%ٺ}RL|)Lrxr,$U].W }1;K"qD+nR2˻LƎť/|?ǵDӿyB>1V#-ukgϼx{?u\B͚j3y}!xˇK{ x3vuys}ZYzoڳٗk/jogБ 8p"b Ԩ Zeꍍ?~R.DAzhi&xX7CD< Ri"|,fV7o khT8;EU5(FPBA%u[HI"08#ة#5Rr-Gg}^y) {ξ?az E XMZ)8 @1_/rrN$džG2t>K-ˆpx7m\ݶkm;a,(7<258<166L"("nnjviH STsG眻ݚ͝B&/8@q}/nau+_(Kz|)OsECqۘm(dE] _cӴaK_wOԥaQ[_}l>3_g!8v:][}jۯ/:.!yԏ\$>xP/ye2^;i}WIP_w?QKG(ejS:ތB"fwwg}WճE; 0p1"YKʵwn~;wtxhbphdj>f勔 \8/8"@ qquM{"`LU/sm&p1d%sC@.;*$0B́T q ] ;vOMΏNMMOM 3 jr)pR'h96VeIJbB>KQFg=]=}koh\y4 gKkŲ ,[S:0& AAbHKDAlmml !nsD XEB1SJ)`B ؉񑅥D)Jc  "ݫZDιe\!% R)u'Fćw}og̢Բ)D0e'e:e(˅^,|*I$3lT2)Jm($j*xUQ~_FP ZBP! jJ8p+!Տ}/HAe-"-Q.1_B8?\1`Q+MM,f2Y0*)~? y]^qi"+,#dIR۲}>b.;02:61T˅9"DTbS6-Lݤɳ|^J& r*S\ZʎR\Zێluumʱ^P{loɇ7#!_=j\= s)Czeo;kvf᱉t8;>mRdbs,ݳ[7nqc+ g^9p9($ACÀoU+oV6wsq @-T3+xC:zH|2%oG5~M kwӳb'gcɅRf).KFQ]e6c6 ،reQ,[۲niي30_&EHK K&N2U\9Pk-ϑW *Lz:O -hiaL3$Z%A(3%oL;r^I,ӡ` A{5{SoG4R,Zt~*\BQ5>UiF5v6L;_3BX\22<>9T2GD&-Z&Lf6aHRW 2fdr|/Kr"JeK7B޶#I$YtRݑh0D"acK%{4*pl9ե ]R|\ְTڽ5,JHSeMgblzlf1Hd9˶g Dr2789~W_wMѲIDDj<̚-KbV=?O>XĻ⯾S[5͇m[m9eϽk:Rvh]˄{]U /Ff6,T鈄|^$ܽ!p|ׯըy KO>W.YȷAIY-1G!ʁ%El6uBE*J'_;AWsߧ.0@ q@A&"9gQ8bRR鯾ϰn.ImYMF3A(Cɲb2̫TJC͈f+VB:Y2UF蔇 ,;= cCS6Q.륒˕eTSKe 2 `0 K%PQAEXW$HsH%UU]UeB82в/{Ǘ A@Ny}Wrb|F_(;""1, !9UwLs X}g{}n{[ݽMkvy.<$p8!("MWo,BLB1*B~${}bFG={T.Ws0. ,K֨ gRDmDpnm9m[ulD)FL`.JeSCFX*뺭VTeL._(\/bĸ Y.ͣBasSeE%RvDjz~ufR+h`[[H`x||qi/qQƐ?jc(Wr3)qEUQdEi.821>>5H2yggӞ6t 8bF8T\ө"aM`2c?{q^$|Í+g=.5B2G7_8˿'?sů<ݯb|LM7<[^:^ g׺:S%ĉIݰjpi^XHôѾirݶJϹGcg=wo?|<ު_}/|_{zF|G|@ P[q쒣D͢(c<~֛+@jU*،#DJ@He_x/ԿX8,pąD˸h aBR-be[%W$2J$,I<!Ԧ!˴1`4q(q1 ,R_ƈWOS 敽=|FMˮDZܸ9Gxp=I7zp<=1gW߸F:.2. YeW:F!~ 8 &s?zxNp]7|h{X$JqYȽJ3iraR\<3>:~-%ʷߒg4U12%̃ DE!88.K&Z`IN/g7ffʟA@DJq gTMJa(GH'Mm-[Qtpl(` W=d72H21F&ضÝ}_OO;B|jbb~va~v!HO򅼀WryPYHb$I!+thC=mp%#`J)z(gq`hbbX*+; ~_iZ8vlx=qLL(nM5M$)MO.Mb|A&BwSU;:,rzq5A@(ǧv{|两 Έ?d8rByrԽ&4j!zgꗴ:.ȍ@g>oߞsJٗ}]3l+XA>ohXSo?5wc׎[y8r9Xd--5qja!G'2bg=H_w{R?s7]EH=wb2QAb /!N9j9,qj<,U>`Q n Xm9+薕Oˋ|֫ '#vȩJ C!WŠen x$aaU6@ 0p L]&W~*Vr#\I8F)3եSI DC?;nz-: z.sLN˪[yUsnUtj.hhpUqۢ D8Dbf!>14:O-:LEW}5p0 \6 `|:/M9M3=s @wW[ }KxUM ۡaS,UȗRh aةL$q ,wj +H> .8f 1qۦ"Ɓ9ȶiвng2>_4t`z7^OSUC7tlXee̕la)M BX,f&f\ ĥ^S~흍Ku)K&^nf&3L b#G;9˖ a h(֦V4UQqI08\"L%$6mXݳ~}oKssKg0R=/5z4"0T zB Dn|`f|`(]kw5gf:.U-R΀[jb!G,d _`!>}GZC4cZ"lZu5[.ڀ||ΛKaMg}yq 1<>W{vv\cm-,K_]PW{CS4P_ 5bϮ_õ0CsWXS{w߲s篾nC}豽]uW_{zpG{1ė}y82dZG\3lTx"S?'6m^禝0{M38+B!!WlN;~vR1 u/BUˏ4#؉ [VzF"S*p;񹱙+oZ xbo vAb O566ns9"Jh&_r  Ks'sxTFew]{+AYSn5ZǦ:kR4?K/%''b 9f3YbpdwyYK[jY7ObHSSnrunvnlk'rEL pEqxϼ>;O$l^\Zώ5{@Be`8#2)CW20\tgl?|*vټjS wb9g 8˭coh>EU?KFyٲi`CóhzA;%ԉ00lnj1,xa*9ۘ=G8C 9,2@!FS +QD Ԑ5n{ߍۯۮ*" qCKsqv].Mellaibb,606:'sj[Lnui.Áp$nlhy<ߧ` 19gs ^gLO2[́`Bb^#LAbv`a؉ڠřX'guƀwWWM֯dd.)%ô KT&.Qj #(kxa_1j(dyf3 ܡz٠D7z Dp=@ `JDD:6`%QO,Nn ]k6x^B1p+KɂF0bS#s|8omذYy&'B QP[- - ^\@_^SD2WWESOR*w'sϼtt-ښss,拨_YGg}\Lg +{aܥg!GkQ[Gar&vi|Bȿ|?\\,<'!:r5Wljսq9@#)|s9e[ !9_ PQ-(ɒ(u %Ilۦ^]O峦c*fUE>[k"D6q\~!ݚOT0p,E0;y?HB6"SL,e!0p\yY|1g1%Ob\W#w߲~aB)=߮+#Z !?{.rOdMudvv5Bv5\o5]{^wJCsK9x2jcKqm"p|">qr.9KO<(W~eg_[(s,_"ZF}m_aMo[-,%ff5FXS_Ni-N^ GGk-osɋ|=?~ϖ Wm۲{MoǭEƙEqUƑW8Ba >wR'&skJcjd"A0" YlD"iJئxF&G2.tQݘ8%r[Q&UJ1Sl֭ͦDx'rg%N}Q9B0sD&GO:,!k.?0v'/&$Alno' ?zc$#SnXm QDڒ]%T2o[B7.srr6]J'Ne0l&eB6Mr`ƣкn@-3653;30[.gf[ŽA Iz׭]a/k+%qIXB󅾮Z6]X_m-|p[_g-v`ǒCZz s{;ZvmݺgK5^Eb!OY08 vT rfמ:x|NtC/n^80 EnaȶsI ScV*S FGOqMQEز%1v(u,X+{ݛ*.+&6 i!w{ 3#(dm*ъ⮕_dIN+okܱ?zg?~6v|--CLX44Y~ն[#fc^oyDoLcLd-K;)JyTĄLbQ#cu]mMp\O~;/]fL?T*--ͳ33xnQNo&զ@B"*BvvR6 X( t2'gfbsDܲqZxUGD% z4Mx\, ROgt*+ڱmU9o}n٥SX(^jUMƋ MvY}l(aM&G"5%ʧJxT-8~Ԯ޾E (Ȳ,F>5-yݰ1&~s" Y9CQ8&X%"Icc5S#ӓlF@9Zӱ5+p&CL/J@T]08#EKzSmi[)Ra,*h)}rh~ò<>38_Wl]U 9T*.M_@ܧoX29_|ǟqB?}(|w_/l^k"^B⬎7lKd/kd{k*BQ/릦LOxϯёs>sO[۾wvjǚH ނ sgaaڻ">:sk/䲩5;_y)e\.*`P1[a DD3#(bbj.15mmVc"8۶ T^|5ěx9qR Un7_6ǮUC ub)@᧣z8^pvW7n>px*>_}#l<6%ߺH2|]fӪ[ﹱoK/Ɋ7s46b )/Hp8ҋm\٤qPf)N|g[Q_I-̻Z]$Acw?puo}[mƩGsjwѽzbD5-h7~ Ae B!۲JR1Wz>bKt,/ƦgST"cQDAPY$U#IMFWx]&bg< rlÃI˱.OKiڮ :<#$-OF3Y1LGI6ɩr\X.e}8-m{K MW~hѨ_A y:Lb @Qt"@$QJu’H"r$g󹁡R܉{zvl\ xdYp0L|Q\~YqX0 0A TV8cB&^<6֭^mu(s ==3UJcE;2/>6KGgU~+ [HSWl[Gϝ 1-Nk[7?za!Go|&|vh->k[Ĉ~OKd{: =K; k~gRK\Gk<#~'{/A~ʭooW]뮹buWm B^32~#@T/-Ơko$0- #siuHu;"/q) "Q S17@g--qm=:D0JON/~PՂsl]zC; Hxk?mͥJ|$h$:Z\_o3ǂGO~ૉǒ 9|WlܽG +juUtw+g m/ 7d(3&-fTa0&*(ظt4sElqb:>6=vt( @j'2(g7}kC|5L=l63u7]nm→2 ˶dEr{]˶9#p@oܹɪ"r0)۲M,劅\+ͥSD<8˧ R&%l2IȒ>-Hss84G[ED9wb;nukwwGcb>MA6E 憟M+fi.7[סEps-0kU v`Ə-})PSU#UEPّ2eܲQVι0d1@10 !P\34Vuimk(b+3 s@_TBE!T= 8#514쵡R:z[85Sf`: TZpΩR{hxq1# •ny-VY_L0|Ŷ5Fxu }OOuB+k$=iqOfk||!ײy6_ KUK5k!th*u K:n/_>`lralr[?x!vkp ~1˜P+>@Hp=+}[Od&+Tr*ܬoYr(N0#uK7$AeCC`YG^Yi C318!bBd]KEzP!D\HU*ѷ\ѩiL/KVrXy?p~0eHo=Krࠒ+oI^<1K?k qlVC.p&oKS8A``bچ2nV.Q>Rѐ$Sjs 1%#R.[;~h(1*Ѷh:YBg1 Ͼљ)SXD*h)Hڋ>޾{6-N%9ghn^5orr0+ܮL2Y.}ߕ\e&SS_I$Y< sZ_.L(dZ/Kd,r`jz:LeJtAGŔ@"4V.9 :;"PTU/\J9DmXT (UUX24ɧR@^BTڰ*tmQSWZ?32ؽɜ*MbjJ K%4E' C-`ǡA9ᘈD˶K35=72358ɦE``꾭5|.I"Wm"s_GToDQL*|ٳ $j{O&E׿:$rb*c@+R!lSdC[ps5WOwo~_Pǖ ݲ$}yHVES4ws0>{?t5tr-nvƵK\-fVus:.9%e;!Hw_)"Z:|ssg!k;3~]cػh##͟j| wrErc!g!2 !t""eđ7}u[{ /]H&saLC E:Y4U%]Gz T(b 2gĎ.$^<8`ێ@8ԡQrRrrԲAj&T-y`sGOS!Nl%X,XcCۯ܂0ViF_)x0w?Ok'"\{9;%Gv}ͩLQ:}^ ka!3b=ڰk#|R]/#O{}$z֏=pӝ7D[/s΁Dƀ13He& - 6"1o64uq$OL.J, T`AR$mi[iv12qA !%/l[&fZ 0l۲lj3˖ 9:5854Ep*>U/9w(2 ?;N$bXUu%E0\O:se?;}H;yV69[i]޲Q9„`qnѩss0PLvZ*qۂ6fCMlJer֑Lz`N2dž&晣7['gt۱ާ< 'X|q~nH^{hv~va~q)t{yW -]y[w-M@ K;[Rd09 y^ C,$G $Auk~ڛvL%KKs3xbqi||rrj*L ˦DU=LKuƜ xLǎ46l\il܊e!R+9MA-Ĩ/&D:[!coϖSsz[vn]@E)"2ƫ+#"cIRmfbb*[,MkzۚG1l3L%MF< ʊW"",SxE1Lu\, &D^u}nWĂQZ<8FEb>tlb.?}@(͏޾aMG}^Z!wl "*4U M!@PUHG`֎`LFlQ/U7dDLA9nXt )0Cw@lQTR-Ӷ) yC/gbF L,QʱSt!rn,(u2@ !L!gQ gnڎeێDTٹ؜s #bnM0B!N*߆c18t*e 13M9$>Y:~?{CX2(ԡa:>|6) ظ^ȧJzi ӯ;sW^8;!6eDtܲebpb`htx>dT׍^1* '{n7nʩr2#c{> -L=? ]]7zf ц(N*_tN#@$WnbQKRb!83;;=53?=87% !Q4YP9rb ~p$y5٣n8e0TZ[:떕z=u579w%0g"Lcc,ƐŲaN/$N ML%3REiê=mP#J2 [kd {"^_ (@P~\^~s%/?460 i^Riit1A#r9BaiSSsȒwt]gbK^nk_|.E*SayP߹+!|W0y]ӽP y* 3nk[6Ekdh{<[H*:xr@|;x392= Dp\hdc!W2%%)"MszH6%`B!؎,<6X̶v)pB8[0x^trp!ʆ) `qUyނ[7T +;9L/AVAY7)D<3;|̺uE)$PF#bN288fFLs,`ũ XqǟMmSLJ=q!Y$۲}?}w"AO(S RONf[WyǙZ0lmܙǎmھFÉؑ_}5$]ÑHSKS  aP0AL[-sƚǣy\PSW)\6IgcssӓSssBܚ' UlL\vlm-MnM<!nj4D a[e(AF_T*:\y( EHaĸwhxlB,Y EWu4mYjmo[scu BGs/ò$M$BxYY@C@8L -<<˕흽1rs奌QF:,{W/ եmZ ]8bW{Wl]uk99>5lk_pj~=Ka/j Ͼ|d-["}-k|NzPΝI |C,d.\NkB{gՇfމT{`!J-A8Mu]08C"39*+: jhF8]kU,tV%U)cm9$=0҅խRI/9 !n+jَnYŲ)dp.Ѕ٩~(bN7 -H8¦dsTr~*[[ʲ]*K1 #,mSS1@aȖTD$Ge"B0|&ky&/\hsL)瘝9y$ ^12e'15M#5A8s:A6ENs9}q!?px.ŧya Ec2kG~~vd0 QT1#MQ9|?ſblr}׽. G a93@<.Wʊ3H( ,zn ; xǽ::lz2BPݒ0AmAE'(ҡ |%&KR!r(GutCRXr;Ϥ9ÖA5U4 [ӈi[,JG(Gs`Y%32K@ԅ z9ġ@!:^ [6hE\mFX/c[0SռtG۶cG"9eal-JP0,RC_:g65@  y "W7;=%R{-TrvfV׋cۖhNjX"Զ ml {Bpu}<&~7Fl0o޴iUl`"WwrOz#<④''l?18Gk>.a-lQ$5\ǥ\S{0G50._-Buj-xu=U cc}'=ӧ(Gsiȏ/?7ڱB^+gT|ieC"<ꍇd˹c'CM>E+#DmSܢ0<13<~\j&&U(g LIT.pa;zLgD,?zxlIYq i65Q6.Y[/sYVe R@ *ds Pm(!`f3sz$ "+eG&TIsv{ ,QrE=s5lMD*fۆxֳ#ua<3Y냷]}:?2 _Ůpdvbfzp_~Sѥ醞ɧ@R?x$?5kN/6RO 3\>HP,w~+t O=3> {{.Jb3 x>ύJ*b2Nd,c'Z#qS?yjjq>Xgt`[%,+mMݝQyuߪԼ͸Mp(ށS,'gNJ$}Z(mj {>aeJ/8H ><1ȉቅ%Hлvuu#* zXs|Լ[q->߾V&gb/;qMۗ:.(ka!9??k|ќH|Uȋٙ{Kٹ^R(.2.gV3/h*jm{NLO-hnұ*qI|2eZ6B{\ (e's7FaM_gNf x2[Jr31 i>B604} uhh۸Q i:$ $J,y=]rkSxU[?xBq}Sm{G?{utbb{>A !I>{.mk oqi19窖zyGRvuV:Ύ??>0G_~W&cƉ||Bt}(bBPURn"CU.E;;[X}嵫9PL/}S ҼfCJK9*sn`"R2c U ^‰Q2@&͊]2ƏϞvzbR(0m0G!n:.Yf1_S"0#ԡC)Gq*JnQF e#9W*.flz܄SdFQ27Sƌ/-`EʤR|fۅe| B'e("?6yϼϿ)ۼW8 h=;voY/d:20v|sK?~290>iF:l<_*RXD&hD/}ݛn{{x@s^V6Xw3sdeꪯu-6ڴޯ3|NTGsA_ yZw=0RU=hu?_M&b8D A#ze;Ʀ@ ޟ_zٞ83\d*dq|!nynb1Đ01^XD h>%JnL(^*XP=62l(r(`H @傮JX$v=֤҅PSY"#9fqLE0bɁcWvND`lJ%iڼl3MT,1H㽫:)R!c Y2ԑх¼mđWw-lE(bje}_WmmX~rlhԡͦ>d⁒;"ޙqh-;}h+=3{_;(ne>^ŒK▔| ɟD||M;7cLAC>s6ܺ*/>C Mq˥_د}uו{zY1Zfv c3o6frlb;ـ;!f˖,YFff?zFl%[hfvN{ittvu˗ɴǵv>P]Sw]rFIdx|`6|pءg}rl(XgNQj&(j""# qF!l&jF]KI%nS"|&u鹙lJJ|N39N%D2H$X:AaFS[s9f#P ,K&0X[- 0'۴߻}yґPlm}lS[+Eȼ5&Ea^Nëvb@tيUWQGP,Ȓa(2 kJ* ,V`{wPU/*(L*C{QcQ"" (` 8X!*ia( )MY6Qe9HJcEEX"!YQKt"˚NP*#:dҔԱC/x"#Ee:*Q]HFtX9n]%U8aE6t)gbFQZV$)@4@ FNN_iIq 7h,-9S3O?(1bEybI+dZ(R执i+2 bD8`2ֵKc=v WћVo\)* Svɀi\մb2EU@i#mqY^ajOEfz\}j4d29<>!vDgp_k82H8-rHw]曗WUqF#kGڿu=C۞ZRQVU_ˆQ`dK;gy[",͊{"a8``y#XDwYpwx`$rHHJGv3X `bKRS]SUU4 85<0bp`v2`b&c+\ht1k/il&³L,+:qRBp4)>>8<K_ZWSYRXW]TZrLtA x"Li-pzc K+Vi%=x%?ؒǙ?~k΢7WT'=~ D:W]g!sOĒ]>"4v$޳Q(c5[g<޳lwtѝ7]>ؽP־ݫ4&WO??,`!)(!y $Cl " NÝ~ŲŔa;.Z*OT*Tj5YF_eĩdlm#?0VRC]{0"OE֞D!T\0&dUVQjc*#ބuk*JKL"'{mfhK蚮N&3"°032 MT%nXrkDϤ%A(7 ښڦڲʒVMyѺ+|UGյI9#ǢѡT4(`l0Bd~_5]Ep%Xɡa fuٱ+oZ/oZ.~a`D6 ;*+8[6^اI +9 3 \⪫/*(2X̛sXMGE8yvh4>1=Tɼ}^|c/>KU0O ص*y"(%8ڻ}w{geYaM E`{5ŀ7tx<0fDrWe+.L` ,^)uj*(T U"ivI q4 "0P [ɛh8e>oH (BV%&8MO1b[ZZ[bs ČOj2/B E4Tgv8-)p5VUWحfsL2!1 #*2 @/詴br:7_# 9ɗ?rK<(pu/|h˷|i=ӻi9n9n1s<K7wfUS9Ӆ =@(/ӈ@N|w8P^-/f%SiHю֎񳐔4߾osld!)PX0 ~ 6./,ݡY5Hr&*  KOܻCJ+.W,o~y/LEߙIncdt|zddj[ID ,|`d<P@SpG[Һt"Juxщ0@4Q(If_%Id ר.M 2tGćo,-ǙƷ[><~ۧ|S\"XZ}>yݶ\,XnxkwpbQ0g!ORi)rP౟,dg=[Kym0ŋ/h?eElh4 bY@as?=7'wMhd2ɤڟ);wt O76Ueecp:mup N3ɘ&G+,wt`4ESJ)fEjFFuV ꬱr[U%LI|n} UuBt(D)tb!p) 0@)40846=w582deAISTMu*T$297!QY)lq{?=71Fd%N!Q`DKq/x-K>\dO몎1jU+/޸(8x6oۥ #DJ(UbjlnD:IB q&OO'br(:4]Lfт(E,``8(Pj*uB4P$)FA 9 "fy@ _>09uwm"*j MG"jRB: Y蔛 $>;46IlV{]sesmUIddMF pxgݬ^A{pպǞݓK> WYX3_-g>cA2 lc4Z< ǻ`8Ƴ6By0sF5pȋ{%=<ܵSVJ/$Ouwv nwgd$ugl=QJs'''Q ' 0@LTYZtH,y7a%Byֳf!ct$̷n˖#s593\67DA8whl&_ c9ijp]Ox2}hct9ԝc"ܶʏ߿|w;T4"ED)` }8% :HL&ޮCә$Ew:VnaVm m/AJJdY'a-185A654ONph:! !D3փG'fXR4]'Ѱ?3҉! 6)MMJL2SX+^$RNw޺^^\2&gF $&`8`DK,`\-*1:IFKFӉ>SrR?8! 7mh ؽn4ɲcRL5.\];?:i;jSOw羃=UU%gQǾ8Y_Wby_ZQ_ğ2Bo}}u<5-t*.ɚOLLݹ?02{(4լliv YCY$=3bd?r煗_hw[&"n{*rL:~bDB0P('SJ*?XXQUR2M%K|3 L+ PpQ@p|}' !Q꬜'ڧ tTp45g%ew(H\j{sh, !%ƺRi28t]xF ,*N>tq\"9U\yъBL0 =owuyQM߯~hj~'!7gӘL>0ά^V/<޵έr`d*Ӆ\6?!s?y%Xf s?sGb3ҿ+V+ܾ4!+7p7Rmt:zVh9bd{$l۽bmsCS"Tdi x\.Nd<5-&N`YL@َ#c>? UjY7@VcHH?hjlsYvNA PN\[_7ru30>.r&dbP<5FDHw|cXj%:Ef,nj,MꈪP,:2 ?PJl0̂e/,t9;aJ}J/\¸: !*/HXnO8T5LeӳGY3=`5e69mPjMI'c`2\[}^WZ0 y2"Yr3 .Ტ_iR+.8?= f驑D:dRrjld$0(x-}\~?qGtZj`0_q=w &մq%EOOO=cV\iu07ȝ/\gsX(GtIiX!@1})HeԶq``>T$ D91gfBmGi0P4]ve,Evro^-xM0%j`mHxNSfY:Aӑ) \q=I AL8. lqk*Kʊ  "hca %Kjg;8uCJKy>xuz"3nI#ׯ:}bF.מmƝ5Elo.ྡ|Os٠\@uJXN!j+9,l"U_xȟԖI9so߳bIuKSy3B t 1|o0l f(F D2 |HhZZ[bI}QbG e < p|5tk*Y#*J)=}vW}g,n]7_?o1Нly9p,w_W02>ӻzŧwf??3<~ןm οk8_y[wp2i9³탹l^_]rgH@<-86Zr*Z+^dJ;us#g2t?>/-P/$"1)b("3q[g(4P?-+v5\s{0` _),YQvCK2.s=hߑ!YN v*c.q#C r ( X-lra b9sQaS}}sDt]WD9\nb  jUWt&TRL<Ir$d2)$`E E`b P0 XDmWY G T8'B!)-!MdMѰB崤Դ`Yo2R$2!uk5F{NYZT\\[U[QXu{e\li(eucH<0QG8&6Wk8_7W X/?3 ݺlͪږZ ("t \:xw9851X噣CDhuǎu8X{HXdyu%}cmUo.^___]Xj`<`c8 UY&D̿⍷MPBUJ}vb}&³` צ#(35:psщ,>9<̗TWddKFF'**U)IiXify(yt2',,ńtϑ!Ly}eՒ ۤIh&"  H(BP` =58>=KvgcUQeRDDl9 [Wh(^ RJhxOcl ߿z{`!3qſ/sEG>zM^?_iErp͗}cb8C]㰚dzqk~s5ML戩P.'m3|Vpg>|-h<5.d}>sf^^`fh_=/ߞg! X踢t^^nb` N@'?K46W|wNuioM)Kee06[͵MU U d g:tOv{m뮽Mu-Vb@ %rȌز꺺+99;*<D)nSz媘.S]MFRf" .ne2FJA@BS`@@TUTEN/t>ʊT%@㫀Dg`s l;2vwcs K*J*<MTԄ` ;F31x6k,E,'<1dawVU%-O>kN(ݞr^_U@mCH4Mv xq˖5]5MCobvعc%W\.p"/ U'nM,n絗]r*]ׇs?8576=ǟc2) to{X0`t[}eS~)Szfn˗LB!Pec;6o/qۛj|F֔Fthm"0E(n?xkpt<15c_~ +3qwhXg0Z&c*99> ۺh/b؂9x5 ),lxx2O;,5KJ|>$ <E098'Jg&j8NSf4ח_iً>/,8sx}ow]ӵKɔ?>5 >09MX>y#{u8ΩG, sP_u>J<㽆Mk7mn?փ7#b F"tG&ffF|}K6-fd-/ 7]V ED" ƇƦƧ{{z-Ss=b`6 ض,f/uFDbĉ`{fu_qU|:^T"gC;ymMndҙtV" Lʨ}caDlYM\et\zx jqLLGc .j+α/=p~D&#p˅B#yISLf4. D 'PDd.msٗmh)) O&5F")E+:b=CmCc`#(kj*3F1&^@0|[4g6SZ/h.7r|sǁ־\69\OBd0w;zt|N(ɧ1s-M?;oog sf!N030߬#GƵJRM5hx.wk˧YzY*Հ!DΚjEVhǡ;ev.V-/IIcTՐ[XK", z:Bcl2#BL3LxA@[@P # ta@xC)ETJ$L"E0 CY{b@Qlc9QF k"ScbjyjK9U[j.qf(1 :quBoiiIMMEmSjjJF9>>=ne.4M5>4UVX;>p5ZV 脾'Bl+t78-@`"1Yk=t1 w#x]yU9+r" !Y~9#G[սÀ`钺D&qСVhf&Ҍl+owRTˉl}/>rQQlTd%&B +hb6pXDZJ <΋/]͗Xp B=;d0zR(:\t{qσf~)dONgQVJ`hDu-uU%3gKPE9r]RD`,50uox|6i()+:6 rꀐchʱnUxUΉZ *vM4p'cy٦eis;8/h}=^AZ!3Qʧn?| fa美7,}g4Mo1>{Yjsy}O[]ĪcMt|z'c9{ew <n{y7<]lXphO'r4=/V}?9( xmZ~@QuJ,IX +,n7ҩmv@2cXײai,n?9xspxtj4SDFqQV@paU,unث-+NGLJLnۮ-*c"4<1<{G7U85%% EQۺu G fdVӵ]ߴL4"JDZf %L2e+k7nj-DbSr\2K.33v=׭% 9}v` 33wt_*"{:_j7*Ll: Ml3AQζYX4ѲY0$ 2#@?M xlJ_y=׿+/^a y9|c=S_ ]1B@" #j6+k5 /NJ(NT o$Q`<ž"4hb-O#),ņ$XOiTׁgTUOU9@"R"3$&u*#7YXp4CHbzlz>1(Tz\BPhSzW^y7V`YpY$Q_tfDQ-4 ƠQ1z:{y=EⴖWҊJ" Aww#?::=j݊wKJ&1MUΒ`LRrGccpחV2G"zͳ# ˊDh^:dJ>xgh|:bTs`ZjyY J-7]JRth4XD$$3@")_T?4;\fwsvH5Kmz 2ݘI&BS#hLH([(_bXI)᱙${\Ҳr02j6YX!DNޯ(/=g adu:V7;\߹?UBg6  |cpҚ'>c}99޳u">#_y+XJ9YQᅗZ,dn֯j8RqzSزhkWӘK=;>vG尜7yz!Q oţ MQEYPDu@LL  UUDB 'ff&_zrjT8W_:yQ ϘcvST][vSK2im`v`+vVW EAB@ ,F(aQ PJ(LŐMϼ"(QJ0P^`FX8^V^`0 :tRjD2;汻[[f&i&=58˔Y$X B,3ddyhxd28 `j˗&0"uEiD2ĪUX$ⱄ$Dj:KP0> esZ91W-m\n^T-0D@ 1`0|Fɪ4Y,!ڏ9Lh-8Dѐ "3SQ/,.-.l>p`tlK/k\n'0i;t=ӮH456uUn^uЋR;[nە M/m)R5gV@HCc#}Ss~0+ jKB4Yvw\ӲBhX)tVy(TX0%N ň)B[&͙υv qCłP:U Uͫ\.LJS*ɔl¾07I#xr.9pwp2,&꺊bYLa @z#>p#QuYm]хg zui${tM׬K<~O&SҢ#=>q}0%\lâo(W+6w-kg`X%UL.DmsPWΛɝyrqGTK,ˬ^^dxӯS9?,b`/"k*W'K^;&BP=RFKD0sFhm^^RX "I˭,)+JX Y3ka<%-.[7;;vcxxkrlr5Թ4Z]hF2ń*NfhY]Xbeay@r<ƈ2lDE 1 HvV@VR2`bHѡ}]IHI!I-J'bu%jR թF6e $ u,VN@8rCL6qjMᙀD0իnd+pSSo)NJFSUyn. ƂI`GdB#Yhv  ˄D˨LH@vlnmBgjJ<<:bJ0vo.hw[)&b))ovKn)ٖ @F1S5tOw '3ICuYq]yAUap|%]0Qizy%Q`20EI֎Jg}eiP&'#ss ˶drE?67'Fw(¼URm=Ó3!LQUqiuyyeYIyj6b 9 W@("(+{N2Ysޓ|?}b Bh.7~o)?yǙW?{ܢŹ ?c"m{&6o?;ls╹/m;.ʋV=Kߑg!'ߗ•83O>WQ\"_`5U80~hP.qںFgwRMg9!J A,[: Yȷxa_ X#" *QL&)ECn)*,OT|br:x%˗zK}%5%eQc(>n5ۭ.X S]S}e뗔J+}FY0c 0aU0, Gl|[fcCFn8D.iHf$&S5FDMtOƧB7o&sTtxjcd9"DӒXt直X; 15B0HPpV#*i uXf̙ 'gfC`drbf`bj<ENmEr-k/Xo:1{Z(ykfXSP0 G]XL]88,:L.3MU:vlXaJy7`c6\dGD\XqEW\[njni2[ #?|G*h*EB(A,C'֎G8CuTWZW46W8ݎ-/hUaU"DUD J MGBR̕X/۰eYea/2 CGFf〹eu5MV'!0j]?'ySNp2 >50:6HziaQcuE]EyIqA҉c0rFOXoWflCܯ'㯜E|#EОݤ/.zC?Ag^[t?=_Ƚmr9}טMٟk.]Kק?&_y+dcA3⹰h9Zz|G|0^:_o?-ϲ96W$G6s&}ZqL.qzo?#' I ) D)Q>0ƘRX 6_r0O"`J YAXa /VX^髎2i6G±H[d]˗G/ ~#7W^nƊJgLD&FUu5NYWRT8Ҭ(ʶE(ǺtOɊj7;TW.,M(,t]y`0q"D г|"!kuN _w.<Ȯ-]׈}g3rlXոqMc 3o|{U}{?v7 DkNw}k,fC.yhK݂A/Z05zxd-Mmߝ 2W^KW(\ry֒ǹǾ%֝,bLka:׀_( Z~ɚ&J F5m],)}lh2ģ?:xqe8D  h+Z."kݱ}c#}wlxxKݔ'tXq2YDy+ƿ' Q\Rpf[%i9"9vDhɱ!Uf3Mv-F9=1q$J.5=1 x Om݃L)&REрPqyAaaK[,\ [N"UEONM~T*588XTW鸴Y]9E@0τf&C}\,>4@bxZ:GuML<'D\$2'rx-וVa#D5Y|OOЊ+CNapsgt:UjKT8|.[sLDrה"90] meELgtUg0Ϊ3zWĶ}^eIcҕ-ն"7D c@xwh&UWV5U8f"+ىT4&%ST1v%}z.zX{L$7Kj6|Nn3S*sk0p#/<0AAwA| O r}% 9[ %}si_םL]u}˥xEGퟶv5LgsqEuŅs9.X sŽ#;_#z-Mg1 B,ii g>t}|{du0<; R[DRzs+T~5!; "D TIeD`ˢt:N޶{wZgS捘b(0V4J|7YqYgk󛷷5wlfjTRuFli6΀|tЃ DUK_6i°nƒ<; +*5e%f'8Vpfx`j6]\lt`FNLhbXc J*3~9)3֔M$v--plZ^U^\bvp]N1oHp*nw(3SPDJeNa'ǁ"Fal97748n)/ϰ,@Xx3,3, k\عV=DlE;y6tC=L?kxncu,}I*->cٛϡ$_\XH=oγy[_sa9>&̷x2}#о5_fó?E1<Ԯ\wڳ:9L ǿ _Xqde!)@ APJvi[+Ҭ]vy'MS4UIhXA`zh"4wlU"wIE)'`5(DUའyySm/n}2L%tLC=s=Gnwr&ўCdl0")**,{c}}` !炁`$SdunfQظ02 E]8}*4/-@FCÁ )춚 MվK*+NO`a(<B("PD BJ)APຢ*oj۞c35ծ\V\R+hX0@1le@(U}|m4>2j'Z/ĥ ╷pO:-d[s}ٹ9myjע}<Ͽ%'%]'?S99lXس{A|ُ(=KC4Gv \o|s$\eqag?)GAL(LkSG;:'&g2ewV7Ty\NUE4ey@-)!t[ .B;{쿿vzD|_\ yv,i(u O~׋}#9jxs.w|q.,$B7_mWٌ}ZZn͢B<|)~O"|Kss_y7MW|\e;<x\X~Xy DOH@A (4[`("6ۧNJS yk,xkPDS$)`D̋VƇz:uU{@8oʱ<'7hn.,^sl,5jn?34446E4+eYNʤp08Ⲳxon1 6 kfWS0*|"oCKS`r.8PE4<g^́S»N7#LcBSI*(M  >hñ$("Èh1[nf*.-t\%^JA"c]{XMby]Eò_ihXA{?xo_mb5M}w *婴<&,2G[h6Y,fh.8𭿹=r6>k/|9>wN6_{jݒ(z:#/|2_g3X.J΃giUl=Kvq.>Ex' z91[xg)gٿ|6_0oiKq>x%<љEo~s8_3yXR1Ɲ@;B­;(Pco"'}*@N̫ M(@n w%AIN>jʧc=ݏ/WtIAt^Pl9SYS1 y|_`fԮ^ y4 M1JR*)Ee\ycsEs5D36jI]Y: 8еhlPUR+w]CVV' Bl.s}}력p^ye(;.MUG{v[ E+k5J ռh#MOZmeU`$pLJetMGtRf:?0N bJ˗7mv # Kx,QEl+ꫥ?OV7) 9Ncw\ sm{Tnl˿^VJbUKUܲs6~X(]iםN<~~'s ?]S[Uس!l6iWqosd9ɗy41m7l2;QO}!vuW54Օ[]s{ћOgB#_9eMbUy6Giw24;auGQBɣo8CǤkjZ%RZJ9ȋr{EuKess>Wz0E%׮n~b]S-09oÂv-#dہ~%*+76+ox+k0fgUYrٚy>RH&hDQeJ59ڧrIUɊMNh1E@0LD‰h,Jk2Ŏ3H<Bc00Vvx=.Y\ZҢt]E6DN&֪%5ٮщh,YJ=En55+z!^A=aZaמhUeڲ㵚3RD)i(gwhU^^XX{|vECBKQ*i]ÄbH,?07 %0ʊ*ˊ .c0B2 l,G&+ v,ב`7]jY*y/-^ӿ,k_(qz񭿹-rtb/Oz㎉O0?_Eۑ#Ur=o`rt}X p2_ =?͓_-R9%_\i߹o#/SrL?!Tp W{EE %x'YH I"@R Q#$I)$+l08ENse"_;i:yy~DV_QUQTE$TD46oyVBV<ܮ fPZQzQp8z4KRiUUT,mծt[L"gœs,p[,J)I%tJ#;7 82wuympACY&Kt:Iǥt8&@$O$SEF`g8d[a& pFl6 B#Ss㳳Xf2V,-5%KKj5-*ia  y꩛0VV^qMK4 o~wr|Nj,Ţ\4N NLLL[Vs55MFsv.3{0yK W.[-h(0Pqt~]d0:04zwdrfPZ4TWUUyNQxh*`*c^@@Q}愥uʃz>oŞ%6oz&xP| $=п,_'"8شuͻt-:o~]gevpe+2zCϿtKW+ݖOk ;k c'n~co~Sy7_-/EO._P^;se^D<{"n8W"Wrˎ< jZIxu!)PPt:>6J *"AILd D]j K58Vt@ 0z2fpz92?}'QMSi9r6|Wb"ІVq2^QL.Sǚm~U *q)WUx ^+<ŀs&8k("Ѩ(Hj Kj+Sd-k2F"Fh4XNRl46x<fd&HGbh, 8h1`2CT-NLDgi[j ˽Meu* |F90T 'w:N2M71-m]Ccj$o2f86D"Ѩs/-x}emSXlΕ),43ҟĈBEY+l("LwH[$).f„h̋2YAAjP:ST?|v=zA!W<^^YG+jczv/[.+`(<Ƿ]7{zZwOxw_k23_ 9/~G_٘سN{N,ll>q`kɔt_ c̉/=>{M};]zAKQ3_?'kiZǮgg_<9aœǙC.5;BR@S Y?˭cNC8,˪,É۷XԼlKvF?)@:f B1! J$`iS^ڷpyu n0c3cHaQ'"fSj6؅70EFȋ5P#^d ,ɋ2]GIcqױmv'~XWRU3yDp2q2h6#gbab* e-jF$)ģX8fHfv6S%֒\ZɨD'J C UŞ%--eU^O곳8 I*F("LK* uGgSX< c BYqeUm2<69aESQIitz. C `L4? "Puvb:|X_wt8XubD< "g4#aD/,8x; [zRz,يn?]CUw4/8]nX]>zJ=K(_ _a銥G;r 2>=?bbB\6SP:K-f''9{'=>:'>-D{.K~/xjˁ?6_H%ԗ?q#7wʥ5;u5B1Ìѐg! ɒE%io߱'wNO 就S+hYe$ FYIz''&;;C~ytdtx6^pŪR@ٖȓhz7 ٩Rѩ\D ϳ.}Պ5v;-K_i;N(!>OO` Ci`ƷL =Ei ϲS  !X#6,bs<~J=bQ1l08esGt%]SUvUCql+B "()4fs 3& r a̲hxmBTe$)fRq9JIhw=vg`70Ou8 @f{g7҅xi?ڜB%(ESicʤ⩉`)W~|ke>XІ gz_ ?Gs#+ꭟs2-zY-m|&͊%kWԽ IQ;?iu_\֮ˑ|kFgy.g$ pPmzsO /.*.jY\P.E͢aamk;6>8}3m[F#3v !&Ben^c,7H*=7143̤4EG(X2rekj|sܝ"DwجFDB @tp(B4p.O8Ie}Pvb,qBNO$聀J ˱h|hhoe+V3`Py{ֹ(K#Du&jZA óQD3獜hprrQYU fŬ` Ǔ%QDNp0D4ᑎ<:03:0JRQWQ^U"Bt2Er,<8ttwrbfNҕB(P"`ys1y7V<=-b 0%{Tp_`l>15M>02Vx#g;;v7m|S]yok^uHખWZ d[(e|MCMy]}W>pp;]@ig`xjGjo|r36???Szbi o{SNV;>7l#~zk\S.ߴ~X.o'nxL0o ! Vֿc^Ңb!{0&"Ou|~f:rɪU.bC˚f)r>6pX퀐k]Vӫ׬~g=7K =>2 o74 Q@`m;vMO g9.1:ƺ"!DSq*j( j6ɤ3Pc59 ypAZ;@1[<k(& i+p_'G(*׉tZ|.tZx(${͞߼# PD R)DNIR ƬbrA4a8#aN`XDIR! Q5J)}x[HO0nNIցi㽥E%%D0ŐBJƦCsLVں2b2D< @Afx9?_L䎽7~hW?lȳj/=`꫿8Oo}ߦU= _<}~O鯟v|e]zAK{sH~Yƚ\ϞC=g:lrҞ}?Ͼg]Ǿo;-peg399x{|izu۾s`m (_<;<?ӖYSw_}-7=ml^g!_ vhT/c[d`t77ڰzi a:tr{Uax[\ږ?w<73[)nY+N3<!H$_8JmEeui(25UKKO=|l}3,KC JEAPU-JS]#D%XWTDtDvn-z:̹:i4"'4#ш,HpjbsϴޓIҌ,u WfnY]AȘ4?Ǜ}(Fh:f2,YXaYr)` I,BAAHGHCDkg+@ҧFGCW]_)pT`h4O P@% c MF^V䮫)l+w`Tk1 {XWԖ|d|9kOp'g-oxJk5`{|:/Oh_wDp] ;Zs 2O~+I_=qh?cK_^[J<;ͫϓ0;_L驙~viwM;6^͙0f1F z'o3 \QYQiQl$C(_đ|=B!RaD^jS{̆kV !WBd:ԕ͌]h6|]])0ǧ}O&.Na<-f;/tUyM2v r&ʐ ȕc|010'uYJ!Thɤ.I/=r*DˎBeYM2Plj:0>1788>4410015 ǢiI02 v@QBa0 AP]Q$%%QL,X*KHVutYG:3JX;"[63vηNb F#aa&"u˲Ҳzi:ͅ3i(6R)bI?}їu N*^XT_]YVp8t ,DldhS{a8EWס^#_@u.y1# ۻ?{Gqߙ-Owr{dJ @B B$@ ;bS1wYzvwN,U->`˧ٙٽݷ^EJVr] o6_$Sf϶z* nj[ XUݸ/؟j;a_<D';l/7&U*Bcs;qV㼡Jƍ=o~mwZ8p#>j24wgHk_|3?a]+bEeF{k.>S;w?hKxMڥҗ??f\N}\>F[qx?q}5m F^xB: VKi(*Qq#!YƢqEs*6hy6{BB@FM_WXzq- %굁>]cq{1>Ƅ$HQ$ 4ƫVʱc}C˽' Yf&M=tH>F[RXT h:joaa$dH(dҶ H !dFJĢHH@-{ JF,ĴM?K` i j!iADyۥ3m̏ky%8[F@D a 3UNh)C1[xnD(CI!)p"LSX,&-U3>|oo(.C D&X<5"MWl6v]N{~wa?/v;].+0ΑQ&uh;rڗP&ȴiB,TTm:Gk6bcH֝U5M "dH@GիÖA C 6 G+C4,@@hgLܴXMCdμ?[Q)O \Rp:Ul5ip.Xŝ hWzfh?ݿp?!){~^} s.jZu_ssiK<>%zlkrE[Ͽv)ôθ]$״̛xD4zɻ6yt]64in os ,\U#lA{D㖾ݍ'RB(Ģek;wMj?\r 6<[/Y=)uuq;*9>d=H>z?yWoV՗VD۟O?n]t 7=B oj6}ù?x9'| ""hܔEO]I"x?-[4 cLond1˖6==dpqM]hca'μ aY*jCX!"Y  B8׀1" jS;vʻ?lld:ˣIFR&P4*N;D$qQS'd*%R7_|1WkV,E EcUqNFH` cl8]Nr{99\qxM0N34iA"@46f皪n4+v4ځ[\Sl'4_ln 47mD%qLRH⑘5Y?.xU 1Æ T<(?) !tI(`deNn9uNڸq}}gp@k rOF TM=憛>kNFvBSK;~&k}ݏYH]S_0e;s O:?~LM;W?gp@?`G_}O7L]>N9}S&fI#O1kO'uw<Ewx 0wq;$u'w^Yx]ݏ/^{y0eOIߞ/[w׸Q75g"՚Y)_?J~IDWnuv~3 Bi~7 );SG*[}!c pmO9%̄)61 PV\ EM zLm!n:n|1'srvW#@@HLDNi(|]5[B&: 3EIFLuG?#H`& ǬXLHdLϚ1k;UɈAmZW>?J""B)ȊbTK H QUT]9vrrr\.q|ntUQғ9HDRf"M "*LQUUU纮usDT5;6EI%@2Nf"EB 0BDӂk[~y?/^YhZTQX0a r6' mksJQS"`q_u-L:d u? ij KѸizO}WO{ϽMwkC cn&K-.9{}u_ v9G:z?^􃿞pm_P嚆Wg\Ǔe+k.o3\q9vlWOM2iPQV'O|\|q'ru4y홗6CQ{fꦗٟ{w#/`">5> G]Z[Sv :?}c2e3fmci39t|El,/rO~ICnsphؖZc:2~nc~vĺPQ7,P ˲Pa,iQNJ[ <f~i(' sH3 2 nVp(3HSJ`I$2"q졮X$`!I )v[ sη_^hޏ+J!?7PUz#ȴLH,΁q-O64$*rns=<<>_t;4M$1C{J~QQ,VZa$ 2!2TD@ ðLSZ$ebW: "oi[9K/LR~gG Xgw:bvu4P 68mcወaJ@Ԗ:ӇMP7~Zǟ{yxYᦳ6cμnvD,lj y9k.>![ڕ+򒼇xyvcn[{>7/z='EᗞsZϼPSI7 ~}z;Qyٍ (͟Џ|z 2AdY럯^@6|uMg$ ʆ 81%O,=ލJ 3([[=9>Fm O&3h<K}Z8$$l/A$FPSJDEXƙt]-Hd2H$LjtUKUy&J{CDMp=7E!  XR G͸($Ivܟf|VEF"Bqĝ^\Ju9$DiH(G"` jnT'|^Wyq{ s]Nî HLt32"Kޒ0Rh )eZƙ~[MH)Z[f]^Ъ8~q^I t*J %vuN[C`q㳋[]2">?k}[?;1p}yz܅3H;+?⬓,''?l[JRϫ3,c\QƧ)p|-|W-Gw,7bhYEYAUuc7o/;爩G (+xϿl&vHK\Ч3f~CVTuuyq-Z_ݿ`IU6;|Ho=?g~c檪NO_7/#ekdD[Sr9'OOQ=Bfn| #w:u ة 'hӵ9=qwx=ʵ5z4@!BEDxꖮ^=vzv;xڦT[^$B$"Ih^_b׹IJ[P4&$ !Į.-&.8%j#HKTD ZCqc0l @[hʶz IiK!4axܔqLQ jʼn3)WX\SYYmbHV&zc[ꂑ )O# (V4dҌcP477H4aYvݦk=%e%v9v]53D`eTBUɌ#a!aDL \[ˢտ,Z|UM$jx= 0fPq_PNT Iv"]/-$qnb!v"2=9q/D@G﹂sg뽭4.>֟U[_{oOӃ rqGi&^wq}tsuꈹWN>׏w)SW-8窿lmJģX'77>2~w։|vDcspsy;\t_{* -@4~6G;{zƦk {G>7zk8Wea;逬v^yg^멮1piUu7J;tܗ.fb:5wmUoȈ=?tjNCyWmЗZ"oz^*DJmȐ/]ZPKx*B:=%"8uY]WQ>4scؑ@""S)%"0d]&C !P(,W*,ZM$Wi*49H8AimΞiG@Zg׶/BtiZIE dR$ $$&a3>|fբps]"B`bWƌN!0\ᚢt,**`DҴTʌDbh% "-і@$7tu_G5v: > ߛr9uæȸ4%G vRMikQoj_j˗ #N}1CGpSƅ3}5%y?msv, yqvjO?xmߟj,\գo}O{Uׄn`䦻˟xמ?]Sw3't}_a#'t#zmÕ7㓯Fsytxhg^<閿}Nyvcsէ|);#GuU5=pz~`=6D)~S"pie]r;BNذa /I֧_|>㋊5av !{55Z6ueַ2oΒ8ټ>a&jj0=álFx LuF Ĥw]z3cB& T )4S-5T4U\t (#H$RHD8ih԰LfLY$"f0MM1,+ 6ׯlZte S {ٰu<e tE՚:v:t9]~1iF"iĢF8%@0\_DZ$I鴹v7ؽng^|\eלUcw3؎1m2KVTӲ,ollֹ:dACd´ nt2DjS?C%?@7Nxt/ӎ}wS{\?Ɩ@䑧3vcs{.=xs{nO~z /=ә??p%gxN4s篼7pi;e̎څI T}s|6|po:Oî} M-=,[|'|Ysy9β=.U/杺8tk1fDS]|#lgMMu;ezK%sy/M~ ^)9ҝε֟`4/3oicN@vէçe"ښŋl%%G1.i{:0b)zE*--fO{䡻81늦X?^[Ʋk*<TAU]DDIIiL3*;mš$ )-)d[q*ZUNCRoQ'ء{1F/ZV*¢"!D2&'4JMTEETUBJR( G3@u TB )y$n0&iHZH !* "c(Ry?U.]P[ 6%iHtdu!]$~/k^_uV{n݅E>4Xtp4`$Hh FHUui;vs ϟt+!K"e9T8o5uI*+3|԰|&$l6ݮz޳x6@v/$lc 0IrFqK_I;0?<%{?ә?:'͕'u~xG^ek^w =~3{1|=3O8`μ> n#fd!3gݦ]s}|* o?w%zR?|o+?o{%w]cy 7_/;KOv{GZ]bQ~l~SvO{g]r;y?K϶tEvID 㙗>{fe="ZLyE>!ɶ.CY$+*-H3[>t2B[iS.ZY׬MBYW_+WU56K!!6++U,nWM;pcMDB@.M;Ts u!{#ڈH*4ne`?tv/,*T<CU"KU+(eZ*"2MSSc(haX%ZDdZ H(*7LrUA !̘kl^Xfu0hYdj; 6gltf"o2֐H0Œ-7T$ G#hxikBK0]nݦ{\N)bI!d2h"75B`$CƦfa Ιt:m~sxysΒ)33xuM]՚5Mфv;w[6fĠyNk dL:PtMe-F"4HHM´'k" W_tLaA΅k XQVpGy~N7b%_6v/8]dM?Kr/;a=cSx4ڱ{tީv'~Tچ{op5{G{@?<s gq4>y* |Cm w\`i+T4Ã/>^{gb{3froy*6`ЀyN]QY?vٿg E}+NCw,d/pԛ:e8T5wOw=).NO_~^LMw{r^3S~q1 *`('_i?bҊoM1%MG"""qF PT`:h4(ʤ#P0SN7vM':ӈƨ!0`m%IV ېI粨ST3~nm;5#/,/9+y`s:%-!e"n @TJIMcѸ!9#)I$\ A)V0TljYS447Դ46ZTEi,v Q:.@ beE aD<BH$CE+Uqu_q;t.# qfF"H,Gͭ`5͖%UUqvMuMdZd G[(˟4lA%+J^7Qӹá:c"tTCv^:t>}hv91󯔭N=f߂ܜ.;o-VU7r3''p pisxQis}]gMxʼnO8mEȴgT3w>)7^q^Gnj {@(c+9}{峞`W-.: 8%?={V^;ڋCH'_}om_;>x==:ݴcn)i[cswG_~};ݲkٺ y?xE~lN;vY=O5nSqu 0u\_N95zq)~3s8)}&"7z͞5:#ƍ/G{J@KWpT\T\%"(s8a (9hIΛ`e;(-):l:( KIs!cl!d8LFV*Q]SGRa2/;~Nfwm6^W>6];1^ O0cijĢ;2p?Z0 |\S"Ev++viH(E[@T%-32PRssC2F-FҨ_[hjhiiE#XزR$L"I(5:Zd ӆ(2l6.+.,0D"&[@0KZC+*#Su{nϛxoQQBbDcX5LD@  FZAK)cEӼ>=F5ȟlN5c:CB&f61>FԳDop>Aov>B8`긯߹ T-A2e/eA9}8Vj/[Y;3_~{kwH{ܯ/9~Zegx`o,t¯/9nWp'ݲhٚh軷>nʄ~)컣x"O|o׵g:?~uyUN?;9tJ5I͕'BZmbmmxiw)j?]u!{>ۻo;छ{*rCw){)kٵ5Ͻ2ٗ?][r>w琁ŻȲy'rM/=O$JS :iԘC(SޑN푻/??VfeƬ_J gpGUՍ=S/|(+λƳQsZHX$USSOݝ_L^e4@Ǧ j9 ?f|9s/[ruæ\P^7"g¸! *%! yOJK'G a:A>rIgҲ3ju0k3 ۓ4Lz\u 1Ɖ:˭T,S5e?~CU!c.rl6i/ cvݴ UUHJ&D<OX,GcшD,jI >}e+V 7mLhWQ:eDfc NWj*W5r qB!Chk0L&,!±h02LrmrI%aw"mlO!'9wf`_|3oA%E%~u#5C5a{= Ǟ|^J0>>9;)G`ƏG q8pn}] M ~7x="H=ϾsN~'=b?|j t'oqCv5SQV_r#ô9\N[ϳ۰C4ySo:G_ 6><''=q?}56W- }_z'L* ) x  42[oM9v+(K+GLx~-EKV,]Yq~W5bHYnO(] @@+\A" ujL2PQxӋJ tUDB uUGݕ2`mumRGidwt6&( $RTD MO=d舁-Gkj[jÁPSCu s*HI"2RaR @h6FMB 72!8>Dɒ&93LD"$m<082_fF!?o#5|Դޞ{#rADiQWt6O_ܦk5'Nsnl9piլ~Ͽj~~}a*7>7["//o>y ]bp/>}iLQ^׃œ?ً>, rw^}w¡Oc7~ٟ{Zᮇ^K Otо0r@i~n"}3{׳K9kO:}ښ9?|k}7%7!];{ovjgsCv^澿z_5vq,}7g׳}.Vv|-161U{I("mv0) ˈs# '5d˖4̛緆3[0wjTO0XsB$ B" -;i^d#0jH4G]]+ˋ僴&\#5b浕F(njIFp(F2D,NIbRHI$ieXkZ'>F,E`wZN,.4M3SdL KBX]u8\N{QRx* 'p(ƒ KgM9.79KKsݚ¤R MUenuMUQ8g =u7bmp킉^q߷Mdrޢ<ܻ۶@2e|OiîO?lA#Z6rXya^Ζ7eZjmƥ+yyVfqGPqK9~wGu`mmo?qSs?O>aZYVG +=bAlwEeUW,]U3eU%pS;BdpSys~&_/~?Ң)F>`K .f%VT.]Y|U¥Us\bǘɸ]} XOɿ\p5e{[/Kg^t{`(}͘cGT>`aJ ޭN4i\ӰlU5nR;KVdH0LPq(/)JHv <1ëqK%Sf"L\ܒ4qLiJ.^Xh%X]`@HDs3v#DJNd%HM+m "k{)-HHG I DRJ ]ӴRX/# (媍9;UoO8}'vП@䳙t5u¢|_iQá}^uB  Gh8kokhqƳn]oȊc폿:S{d n\/svfɓA5t)_vl7{#OvۀWcM.ٞBMòr@y׫v2cjKJNc$%-bR`<F$e (vTc}x1M,Aܲ !މ5>g!XB)EEΉt 2YȶD1#)%@&$ev!*ߧGBDBF)x0 S!L L0]7MSt?AjJRИ%baCVJW14x*jjYhEHkHR$!u,n6KD7(`ٮ;d0Zڼ~QG~!6*%)#755Mu 5MM pp9;GLSsvf' r?nRHcSEUưS7VB8֩ ;0h۴D$?BovG{5!v3'oX^4&vz<164;]vcϾMS?~jS(}ҁ/Շٱ?=YH8r=u*,v9}CYLg'/}yoleGKL0ⵧo*W~C㉿\su탏xYW]pɣz{CwtNq;!^;)s]!_Z #D4TUnI˴ KUoM|'U{ $6EGT@Q|Ujk#f*B \+?7>!v@nK*IR 2<$`D,oH7JdljR۬:ݮP4 KC#`"LJ&dҲLHXRȔaI],銦qEEMe 9l rysDMUIJD4^V]r5Uu֠wP[/$@[zy[3@2ΐV_kwi!rV"O@KïT՗:\LJIm$,I B@ QJ"# 4QbwFtfߏ6r7wh}8 ?Mw+mWE3{mLp58 y'dv=F/'*ر` w>'faoڑ4܂%UdI#w|=^]@yIO߶١BLm>xwog犝S^6ꈳO:0^}B5|Hٿ,~G+춳Hu3S&666 BD·6 i"4cXa+̸OG@̨'( loƔGkJF^3d$t4R;4굡ի>iP!ۈ7zPJfk\4K*ڴ 9JEPDD\[@!,(bK!TUA Ea$9V`nܹAe'oYvOKꛥ ))3kc v2M6BqD}]JbjI MAۦi** 8.Sښ5t,+XB3e&Frg߱c'qPR=S/|]]}? Yb@i{鍏h*7s'd7(=7aZ'}wNOs.M``y߹Ttˍ׿t(*p(HôG`:"D2dF"&3&$$d"D 3UMJj5HH $a*8#ʨ%Ec+i5uGryMsS$+`S:t"RQTM$ ai.DXҮ81h'uUpۭ:qN;sT6)4#` CiWMDbCLrPs#J'2 N䆋9刊-UU\(-|vDd`C04yG{ߏ~z `R-NIBXB&q JeC28wPě;켨Y^))t}9H nnŰn61E}DҖczvҶ"sSm]ro?}KSw'#Y r嚞 rKqq?~)3wd->̙bnݦkW=}kߓ]} /@EY+NR؟#xrs|ƽW_t c57d[RA=xE_C$%EH]^Y9$ KXvα1'jNp$d K;6%Ul6y1Ȕcc:x5fd"#󤄑XdjUmM3vԠ1 jWiu IaqR5eys)!S8k#17Rd!$I*K= ' ,+)\Qif;nf眈X(M$Ͱ&B|$?,Lr|nNAEy*(MB`Š'QDTDȲ(Kpg\,iJ?괎yčeozel vrEq5=7묛{sOM4~X$X|a{etˑ}~ӳie$;Q|:PU=t݇_t_wxRȡ?z1*K;`긳z`Ɋ pO36<8+냩٫&6gӵCw̥sO9{ܙNg׽:'d4fz=%6UPסlC@2d*bEUUA2&)fh+&HAEFJ$6 DXl!#2B,)%‰&myUcBC8Ĵ:0D4y.qAɀ \4䄀v>Ū1@8ڦ01 28k6d3{~jg:qw(kŶ-4at0,9(ۡ?]X41Ѥ{am )c3 b@@tܹVJjlh~./:tiG0~^?;.:-N{O}z L'|ld mw\|هޭN9]A P*iTUU'S2LF"D0SB@4RҠtr d=IcmmF>V|5WS43dАǍ(tW8}NqN;{*X9U+y`k(xnjPZu@}`KH%/oI ),#@bIH>zxdzۺ,NSnkq`͸7lءv>cښOi !=A[q#8}~ؽ]h[}+8}w$tH4!h4O$)IHx*e +}/AFD"lM2)C:H fB DT-` '!V0Ee,,B!Jgsҁ{r>o_$e[5"isoZrici~-HQTB @]{9䈃'T9c` I_yg?55pش" A(PXB+n3Lo R0l@Xd؁hNLuR*li0 "-1q/:e!m72)yC6j̰RVZ9103DD)K&P{P{>=fv‰̤YlKjjk ۝^OPV>j輂Br$d i60LHe 'FbUzd4WwNv/KEn6]}מ ۀ<1s^qqyGe#Ȼ(]}1?QOEvyв^y];^( ö `a^3_KwZcWo;nyϞc~~}zw(+x߿{)f}^mccO {}!w3߼oz*F;zp6~T$Z Me6W"nѸ8Jq S_IJPc'$ ь H$(,59jcGZB5M~{/*t#,j55nA0bta#),acw2iDB"'Tu*SB|¢U+c-Mx]MyW,+* T6pPt8DH U~w1XVlњ3oG?s RҦ2)aEI._u7[Ԗ6$ ٔ)U_*WYH‚]jnq˴DSftU3gCm/q۝NfSrIDv{2l膕[@SsK MZBa#P4Ui%*5:=)% 1ș1y*HܪyCv ]}&w ֬uuUnk>t1KPƳ~uy'_zo_]p1\Y`7N vvyGfGnA?zw?u7s<]wƕ<.cՠ)7cSߞ{峻_u]scn⤬mv*wp湧L(>+SLq%1}ҥ׳gp+˻YIGMw(ma!ٰu01jdYVli 數 nb ˦*|5qqs/vṇaq$b<96O#U QAA(B:["D@h gP3m4%CWΙvTLō˫Vq9laCˋ |:ŕ #iYvU5=ǷQG.Ԥ6Cp #]1nHai:XJBKsۧaڪv[MAqRϟ8Nnk綐dg!$t{l5vg 5o5vVVW^^cw{6(~Ĺ2y%yJa fLSBP1jq|X,ZzH1imCڵ t9fskN]sݦ5ŦN/s6(mp:s=Z%Hֱ]9a6?jޚڹ3 ]4DP¬paӵ+?l/O}gZbz9G=idv1Mp{ T^aC?w?oaθE.xYcglՁ6ڐBoͪG-q1#vѤ0 ^XIɸAF$! HvCCvRt!"b 4X<* 5-(6eۡQI0D 3 ,PlvAC&M̟_rE@ n6mꀑÑ+p6 F0p+T{q pjx4]0sX b1@ -!7Zo溘u&vMnæ%bLKm_;[zazU™$ΘMNWA~Aa^AA~Bj'`a²,qF m1$!o^jey %FVaD4ƃH$d[ X8 G׬ZRTsy8JJs ]LfyIB&;CH3-1gČs䶹n86iV˂hsc.O<(c<}&^J2-`1|NomH_@5yg^tUU}ߩGz̾dAD=f7#ۗ1ϼKo}>x/:ckW,qGCOoDcowם~v |6eϽ2wfɭxIx{qVnwʘN=c:lO (zi?^xmm[=>TZ۫NU'7gɫ}tE3O<364λ6+8B Q#JyjC + 3!0Q͡{}4mi)f;V̳# [#ЉN̂h\{3~[dœƏr욮2!chY&^^T>fPk9654-e*vG~ـw3uJހ24dL 0ĴDR4X4jgh% RU!R2f) kč?2p #ĵ ]QLPxHє}'ϚUKshi=F!:<477*i)PH 4US5x&cOԄniu]%%%%99bEU$P2EbКu pFX4 XZusEy^ݡ**X@$6id[^Mr fMc mc[Ԧw9#,M?(#HL @Qd禈Hr=-Wzէޚ;3k[N}GO;}ҽfY[{Oեn7L7t¸~ở+}ܾ)mSߕ .ן ~̻uisU'Gɻ΋?|_Og'SF'x1dMo>)}Wޙ[_͘K_;7}N=h׌*#{n^{ڧ3+3xJ8G -YFf#8/_4gފ>f[Tٗ;t؎~\QY8쀉8&d{nBnzQN:jZ8`Ɯ>wx': !YIB>Wr'|O/;НzC+2fgMU=tʱNœ=?~O}Arg#O<}$S"-M9y9'OooϚDrG =GFMժUU\.-Qæ)))TL\\N-fz^'bu+Vqu }2ҥK܍M aI)Tr튕0MWWåjcnj/6_R`:EFjHIaA$&ZNj1PEA6$NhMj&6l#Nl`nţW2cK Jsss 2dR메AR* GD (W4M%""3v*XP0e˥)+j[BD  bu-Ս&IMv,//.xvx`.S,!T0 CP8 ‘5KUsr<g(4↮buLޖ6n췝K"Ip"!I%cc6S[ u 2ri^ckw\Y?,ܞ>8>uҨ^c'64`s;`μ_~;;- Tr{eGrGrv쾧ݜ%~Xݜ%_^j>f>{gƻ]ltKԧ |6i-visXt:lGч KVKH9eˆɻwʘ}Yl |^y|ީ'S76fUBC4rQ{Yq5}w?x aќ_/^vʚj ڪ\NQMMw)=e:âg/3oŢUYh\^7yNDҾJHmDJYJ1cFN =t$Dɸi(`DIiRv.Hu@r0=JrjVYÂZ" -@leu$.(W,_U*WDiҔ@a9=.[1fxٰ҂9)[D>d,͔1Z7Zyt:v1[ )H׀,Ks1u]벀6EtJil{_׳n05auWñ#+&:q{M1~`Yvֶ'KgN pQS'Js~Z`IU5*kм<cG7bacFT8zvolU5ַP]ԑ%)-u^kjCG-O|y+/[vp4#[ T2lPɰA%cGVf`ӵo>74/^=e?_1IժuH,*M>l>f=GgvFW[ZMֶW6$s}sв#g+Wɔa.Z+^QYlU u\2zĀq#N0"MD$C!sÔZOT7Q0Mi3R "JэPaؤ cU VꃫZfM](3Re+$m״d(P̪\>dok\w^~8eՇ͖##|F&ɬ ٵy jPRHLX8ʁ(D$4X[@A'ttaI@+U3φ2UFK{eNCf_A~AWZRjeYRS \24M4sH&ݮi kcsȄ2S;O'tIf2mPX,(vK dhKs5FTj T. %H878yni0ղ(4-P$Сmfr{0LosI\) jj Ԭmtǩ!I2,d*'9G:fd;_? Ok_sPU8oEKpՋI6Jrǎ1lc6eggGN sN9(;}vi7-|UM}c)44T5[ӞlU^R(/) }Cxݎ y H sk$hhk@M}k iI@5  }>O347tPI׮ Ms{1Ъplmmsk0&ҿ]!}ծgTVZWT%yJ (ʎmDYq^Vʺ!tMcܐ= iŲDM}KUucmCkm}K(O_T7mŅCY_~(_ZVL)t pnMmv5M?`xͫk=JFbI"Qn D8l W8C$  1ooBDX%X>MR ݮ@ 0z%%\W][W4s)--fsHj['nBH% T2Uu 3lh0H5"l6)/-+:N]M# !tݲ,UQcPd8פi$IT3Υmo7)IB)(OZە[ΑssG$ӰRI!P5MƆ@$Drn9|~w^nNn^N~77[R^*3@OpaLSI)4$"2TRZt g g m9Y!TT?,QUݸzmcUuCUuSUuCu]K0 anyN-){sJ | P-sSx\qޑوϝG0zPd0/0/';Y s)Fd",w١zm`[504 {O>jʧ~?f̴̦팁lmD!.SΦOJ@S!ti+ɫ=X --;'>]S㶤eL $lH;8ttP}h5D̐eBN!R*%l l:rnq IX*'6vhyKȎ\#ש"i9h:lΓ:d i 1v]+,(NeSm q{$cH \劢&IEa*c,`+"W8"RFGi]/ɴ:u {fdԲ%W7؝Kq^bpi% +JB`0h[֮j\VgXa/(s\.WQQ^Oitr{p= ǎηو@P[ȸ+|"%X4*q4JR™j,z2xcBiteϓ6Dc'iaׯ8HfEYdEYdE܁3vi MǁHU`[߅[|URͥ iYcU&I4C%9pHI=UH@BW$#PuBɐq88qNH:l Dn'LQɄ!͛/$Fj)+"HQX7tkĎ4ft]D~t*ގHԕ$/-e" CZݞa{<IHJa 44MuMT)eY(LQ8̗ A)D$TPU9ZIBL6l:$)IN(HL4OsV|ќ@t\i6n}9\.mB}x "X4RV2.Z[P)NIhTjnvq{O8>x,lsA%@ ixҲ1J( 7t}7:X;-s,",",Ȣ[h@)²=yքVS?ຮPT0h\e1rݮʴݭzۈVڀpNARixu!ɟВ4 PIύɶND diVqKJHpv*$A\a2B=f1)!IDD`I!:RhKBF )Hኦ"c>Oyoks% DjC jv<71bhnc+r󜪎[BB 1R"4Tssx_}>w{ QD"Hv`6R&$qDTc3=$f*%ˮ$mYdm۝8g]z|v$",",ȢbXȎԀ(RQ(7}K̤I0iİyx/_}7xI$qad ZC"J U1 !( LF0\6Q!mdC:mLU!S,`@rKPBe%kbQ0iGD?ayeE+!BR&%L@H]ג K KK;aWD&iFa^XϲLhJ7M E*ErsDo C*tnq[qF,L& mj P8riiG02Y;sB3_[~w:p2+nҜD$9c%21!L 0p,ò$R')gD!}5Y3Fqfǥ\!`@ݮIKp3L$c*璤 g1  3`1.4Rp8 GufNv#Wt] ے (MTN5%[6 \cٸnwx7^xmΜʱc޶FP**rlc- |+m+*@X$'R6X!rrӓ G @)* `r,1Ҥ0 "ّKKJ rg%.1Ð`,K6[CS`Ғ"˭GFDdJ0,P@lC/",z 9wҪ0u&̎dYdEYdEYel/$HCnFji֮mjm  V^М2ię'|_*[W}~A}|HL(PL(+2ML9vΘ$ޖ#ҍ!%$ '-YIoV4Zx~Dw{OMU( c)@S@lsR5fR`یD\J2 A*6I  # sӰD<Oqfe ,KB,a&1 ]d6+$2Ps+ c+(%!$I)SqjEu5 nr -3p1UUUaݡJDq%ј2c.v:6dJZ"q3erE5S)N\$$@˼UM w?ZdyqaQـ]5QJHJ)jLJ&).2^2J˲$Dpbuj֞\D ![VX'B‚v)\!dD$ƟhZd JiYd?f Y!dYdEYdEY}(4DzP d"LD0յE*j:rT?hrckg?JD̜ 6f>!b8fu%eɔ%p  ˈ9MW2Md 7E樕Y"uPeY&R&*j(r52n%B~`wWp7M֕KH)LL:l崣e%"U, iJ)T bI$$Z"@I %"KK HH0IZJq0,(I& !-DpU6bqaIRWIlq)I0΄L@cbՒ@H]Nq0UQRFcC.4R2))2Zx"t 6]EbH$OJ .[41M/ '_Im 5Gf6[dY**.PQ/.-)PRPw-`KXa0Șvtd2|1 "Agm'^u@D|y?.[ƥhrǍ(.-rj 4A 1Eeeb*#6,bf;- \z!S#EYdEYdE}J=4yi غP IThm65 r =N bGXޟve͟\m^0KS1a=#P Rc ^ʘ %^pAtmn$JLC#E(IcA4xDaIƑY_5ks FiKM͒2$9H!vUXRQ$ChRQ,4 ZnU%20)K"!P)f~36=,"ZHB$@ÔѸ)RdPaa)LUe nYJ8h ԯ\k&Nۜá%a+!9\Adx"R9]< @h,a)9n6]UTdmlUEa H W`NabD\ZPT2]fjB> %V.$AIi0k[uLĽph#v3%}j!ɓ)ysZ1f!>܏hJ@ ,+YZLH$e)He=ҐYd}!uXVМEYdEYdE}Ȧv5ʄ' 3ek4[v}РRéq B: (oYS"T<9İCjN4ufuӂ2] @1K B02dJbI!`!2,9Z2 d*JD2׍QDmyd-IbBS3YBxR:hAcJT qm4|]㪊\LUI T%T5s2ah4 p:TEq WCX(Ƣ 6rTMU"Ch_LP\AddY2m ض7YDr+k̫ [r }Tn*JE1Zk%v$$H@.9t8fR3ηCfH1Ls~ZR5u}İcA tθ@) I$" )ik!bbMMӫN ysN,",",PrYgK|ߵV_ CJ1bL(*02fĘp13)#@HAFwuu˻Su.Z1u޾y{]>'wڙn>yܼp Ca|hԟp\[5- B ϼc* ׮~' CzGTrrUHф,T,T1U$,˔R qiU]ȋ>VKVdR a*S)@ 04:)NA*@} ?4? `W]3\ Ysսx B˻Nv ΁  qΐ\lxljbv%" q<߷C0T꬚fe\EGA/0@ѪA]>ٓo?x0xtP@q]v0<E2'[$\i n`@?uj>Yi]b*p8wMan7\zݍ`:W-S\ g3@Z#"J Z0ߔB-(Ȥz-73e+YPPPPPPPPP?"j^ܖwz\;Oe4յm|HI(g0Eđ,;xpoLJ2Z„7 ?8u5KM`,N" [H =R1d`1"gOy{WtwwJvgkG8yBy]rl)J|F:og< I??wO <˵#ed.~do>NFG0 ,N0RzݫWk/u91_eZ+d?ۿG˻orZ41ԙ6,9_QFBivRd=t]0"?"4iR!pu>PrEO䛟`z?NQ8/ev֫ 7JZ+9[(NM|F[)9,25 $NT}_T#._ \Lq칞Դ#޺lo4z}S~zV2]FoD\IcT꺖)@aXh` @+ O]#Tls+YPPPPPPPPPILUdjÛU}4],DȋI{}~n7 {"ػ{RҶigb;V%d$XH2i|Ҹ,i2 2.8 A r$e)iMZRE, % PJ-4# K7a1k$FL&'?x뭃{wni,ly21 02Ҡ޽'Siv/y_\--mC\[1.4cCZ&J$b@D@i DQ$iF`:p: b3К2![ҷ^}ڛov]Z`VC&.)NO=y7>?:4ZjJ il>O3Ʌp\Z-1Zx4RG)hV.{0$Z,˄00$LiL@.̽(i{-lhR훇´<߱0 :r*yVka$)GfX xP.6Cyуq3ΛUs/VΠ&X3dYH"I4 "$2nSXfs4@hy㲵TfXv"֤Çã{})ؖEq}ǮVz@1kk4epq|A?\aI4R!_ln2*Wk~UDz.< aB$<$ ӘeǶ@6(QY2 LgYh*43(2@ `@!bu-u:pئ]ՆIʲX04=N&'oy_|nR$! gYo4?xt?O|ڥ̫k[BuIAD9Zٲ,2$IEqIبWlBD۲R( P9]`Z!<\0M\peN~jWvoto~0/y~)WZo4*kkk7jq%Sadp Apx|{ǜ ðhtJ]uǵL[pC (\ |!~_d-D !;\ifߺݽÃ^KDYzW^Y+\-$҄K>k}b bfah"(+*e4'/?׿J|x y:}N@bƅSCQ2G0MOn+_}oz|`7YXtҵk pܽHzf% !ܖ`F1ReMʲz5ۜryY7W%ۄJ 7&GÓ,^jԵeTJ~we( [͚i-ێQR "GGYJd2A4,Mex?Z)GnCð'ƽRYi(WJiaeYi O&FCDz`2JfYk%RDZrdT5S%ۭxzepy{w-@}|4 &eB%^o4Kfݲl"{v|4ѸwL=q8RJR:$LTjۯ]ԍk"|DqojxqIhpJZ2ĴҶm#Na2im&i AklD<;3%c}K'>PGI8OAgmٶcVflU׺+6 'J%>;l`%͛dhVJe٪fhV=ϰ];A)SsƐ#EGȾGk|4Ӄۏ;(e|o6Z;[׮nonm3Mw"Nry1 <߲M,NX)dEa:,K]S 4ǯ~.0ΩgT.>9wYlF5<'q:]خm߸C'>͛Oo{xum;\k%zA.Iet.'-gl]iEh5K~ԸUi$)U&wy{tx<瓑NbY\qRɵ*=Vc̶MfaQ*MҸZkzQ܊l؟&xY<<-y6}swy:ZP,Zѽ[O rk6=Z4?ugyvk=z6}ֿ9ovw6w_V bD*8E\[̻[܁,uuluge+trQ߹=9:<&I"#-47֚ZP^!Ll -@aas2,]DhvJ*ba3`Ea'Ӹ?D4Meu$Raei&RZ+"1ƒ45-3?S){cu#nۦe[)*R#NrVS軥h,FޓރA2Lcw[xfmZ uޢ@->Uyz8p$d`fVZqmg^.  $IJﺎmZ2A%qpJ:6Rđ#c d! TBI)2ir ,YdRTJ+E*$DK/z[ZSEU<~n6Ziz18}v|p|bNGH H5K~)HmrիJ$,fYܯZ+҄0a*r0Pʸ٪eqMƉq,t8Q1&AQfidȃ04dJt$\{:T*4K-rl[ɬ2KkTqaՈF|qoX_v[V!Cw!9ѩW]e_̍kW.iN#+igGl@`ZJuDZ,)0LqLn J*.X&3 npF$ A)Ő1ߴa04Y&g HҴ_-,Ȧ\|`(mФ42fǽ|i{(ǣ`t);iVyk^oktMƋAvt8]!j!h:?~|On{|p[FlVvv666 Fȋ2Ch I5}^- "G5@,4Lƣ)2vkn\9XdA4?|-tb% >q>^B" 鏾SBE4[g`):;M7ɕK1)2"xtr _G߸}G~p|tk_mv;[jucrH%u6A \i}ZoQFVU($0dZ+Z,Hg0| ǷohF=앮n_ڬUK4l~8onfųmI a"aj>i@3 ⦃^ ZK.8Q4w}ʴJuTj-5f:x%$Qf 'bY%2avcJg#4AJ>r`N;߼;='RJ%kvwݦ회"D:_U`DK#!*}`1O,a޸zW[FJi@klM IUJcY (CM ^.kqg Igs`#fD 8C"̲Ԉ u\ ^GVC+{U\ V:Eޘ nzMaR'Ep6fd2 -/nݭWkJ. rpYLp)!>= fg IgZJHPkM޿g(Mɍjlnnoi Hke 8@IG@eW9ٖ[kov&ťk 5]FQղ-7MS/)$4c<ˤi,M9t*S". -VH&ɳ={$OHid.%ZtkW͒a4G0?xo|vpT;f(kiMZSH\mg%Sr98q0pr+YgD I% Sf PJ.9Z N]GGG0\tZ.ol7:mZ 33Q#(O}g8 VRgeR\Q#P(l>(rd9*SQ$q L>FRJ3^I^M[#a342@H "jh-5?Db@ Hpv޽#r@)໻+VRq5.& |NF?LG{Gi ϷZ]im]ng9R-2abq2 <9}{{O{airy{}}wsjV*DZy.# ´-eggRf{.B:7m@@y`YgxJ@>Z{٠ Lwשּׂuʆ Ii㹇p#U7Kv>G~|p2}z2>pt0K49)l0FiqLUPuL °u4MOpHd<M٩Lb0@@`@`AD<䶧5t<;>:Go=NBnOV̭Nq҆wj\`"@M8$ݷ 3z*"" l2!+ղmI$Nq|ǶM64i !C $ 1@2 gCLJ9Z/4,HP+1d+/'؏p%}ڛ;Vw2'͓|Yx-ZJS7jmo6vYBi4/l<ϧ/?gvE}v#@޺͇N(#BV*o],|@ C92-8"0ŕgPe.90! ,BT<9O!cf촺r2m~ g߃?m~<|-_ORD r@x:xb:O|vY"jD Pr}wV_TH1dW/7/m7DѫGGg Fy6tāGGwP0i "BeҶ ,TRqH)FJ ق߸9FZ*ٖiKBfWm\6z[zlnn6/_Y_(oj2J ȇA:o<¹;ƕ hjH9/zӇwoݟ &qͦxuU\fs Rˇyx[>}z2yew3~u@2t< 0yJ,(DfRc\y`D Ȑ!\v!˗rqj  !# |YhټV=G{_D? ~_Ki8͎O<:~vϞ߻Tf>-@Aq[]+kbO__{^%\j90Dg2ԚEIzp8}7߽t6Rlm\Z7[M0y*3`38rԦ0oڌ|p,y n*˦ TϧdΦXJl<}ODzRw/;?[dAAAAAAAAA'B̧ tw)|IIb:IzC@*WnYo,Vr,~NrMsW/^ZRvr2yl7 yQI5Sł8[,LI%3B!@* lj$2ѴMi~Fe wL<6ղW`4B@v-Sĵnu}ky;߹nnvX̛c02ra/( tE#<]$rfIY޷?,Clmot~3ͮm A";o[=J5Ld鳓0L<+7Ct, h4cec; ׵tq H154 e&!)'Lx>ni. ^VLf8$YYּR|ٲG D59""Eg+~Tj׀o|4<ǝnvRb_Y%~$Z$v>\eNe{\;;cJ;}ϦӀ9J^޽S)Ę֤5Vd@6e`7}[&<θ=092PHKE?OGVvQm€Tl$:}WVYPϿG/-VwBj ʁH+EA>}6 䶉ozaI)ѭB<_YInI%Rp|| ?vke[FRp|2̔.d:Qj0 ӘL_+9 ǮoyoTj͝] a4oV*ukonߺsy{zPSq˶h Rͦ?,WngϞo}KW^x3w/+s kPw~U!<Pk<9ͧ׿ѳGZ曯+_r9üZ^ŷ)0 |NFOONf@tycWxFKfE$eNZ*9DaHٶeӔ1s+(\k( D B@L?.{Je4ip؟YLn]mVRk9gl_,}9֦ez*ʓcY4;쒆`2I0iڪ{>SyRiRZF҅w?E!4Q''{{OFㅒȀ[^ty֨ PB.s4,̷MB6+%ar9. 0Xa0Y}߫׼Zl s{QFx>GQP|cթ#H$L (X~U |4 ֍W3D3JVWO}ꗿ x[wZ;;7]*kW˾"WVN+Hj·Vn ےJb8JK [ڵ!`n\z!cQbYӠa!"!31sb:տO}ޟspr#˵>KFU09Zy]rյ9rٮT7iB{O?ٿ?oW_+KeǴ^۹I]dD>z$ '}ڏ67:g-(>+j,PJOLJpFgehֺnm6Tsc^$8gs`g<.đ"T}Yq <bQM'ds^&@DHIkH57F M)/ʒ;N_0Ǔ'OpWv+zq8"f;VNek*’a0D!"fș@ (mm[??yLFl;~]k^lt[ݪ_r앚tixonxNֲww׶ׄ cS)T.D!NNjQ޹SVO}z۽U*\Ҥ9rJ] Lh:,?z|ltp2 HFm7n8 aĆ`m۵l#KbMơLarr=09ce8CdZ)}g갾=wzh$x1qzT\O0F 5f5]SBg\'Llc|'l΃V˱^jRbD읯}+ <Ԓu.oQӄ+o^ q=U `0}ɻ7߽7Tk8rs\蚶AM[!87-# mrl,;cg3,َ8t H~%RplڭȖB"yِ^g&CXIpI˂w~z-rug~ J|.r!h)v*c8T*nQ^yx8=UVS1 4}I> 9*.zWDhY[FS›7^׎_R E=ۛWww6׶NEX @FbtJOA@$ӆ׻.}S&{]ioxh1q9]oW^3[YҺJy>8C}lVv^u%iΒ!2&B-TxyqOf';nR]l57ۈ=׶lò 0&(\eö44 -΄\iGj<^<2L֚Y~o/{w`ؕ._{^TF*كޝwv׷:;kkknF2\P.zccqT8M4 0KS"Zm y)NeY.֊FO4}E@ +<:8<2Xk)nVNf+Fv 5"P@yy(y&D|[j"`!e8Z]- hXM`HSənvj1-jәwU;e"ΟZQKW~V~r8{p:%4Q(TKn>2i7<Ѩ3sy \y/?x~ȃi˙p|wG{3iDavZ\j4jJ 8'H6M! e1Фº},9sѵqLbnld"h d&tkV-ଡ;I/<Ɓ3p9w ,((nG7L4,(((((((((#>=- V,ĉYUS^Tx &4 pڜ^V*vQBԹ? ϴG/x0;iFJ,ߪ"Jww'wlѳ{'JյKW߸ '=PWg~usÓÓÓn767^~vZ8Ft4zQQMS;!C{_R ps}B~*<8?D, gF߼yçAǰ[VYow;W KXA8iABey7ˎYai n,@FX0v=FAGM۱?ϥLEls_[?]dAAAAAAAAA"BD4 J$/$r0])]nloT|D@X|FO2ںij G_덆Gï^EV]omnue>ycݵOϽ׻?w_ֽ;4f'>ѹvmgsyvRuqz}ҲF&oA z)I-s W%%De*~orx<|z{zx?O0& ׾|jݪl65 .8 _zw#bẎh2#֛N+64MYcR$!MH*@L#S8i4inurն,d,IMX%m}9tvU0 sHpbέ/& "H X8kK(QYb1[/vNrh>*"4Q|Γ۷>yr4-ҖazwիvB+9#ki"I!SW̲sxՒڴ0fI 54ea;A*ku+v+lGb17M5%Y&,}ECLfl7~^-+YPPPPPPPPP^]Ṗ5Gf o-1m͸RX0?=LLffkeDyNQZg~wcJ%j8GvyLW8yk?9\8YɯF&M=VtOmpxɳ2m \#!rh07߾ugVV-]rekk(;IϦCO߂e%{Og*SPY8 gIpt<O?=>9,#oK_~ ;rS3{Zc0,vRn& j 4 DR") uA4Lj@1< /NDK" MxX̓,՜R&Cϵ?W#N( =Pj[jjV%϶-!JTuww}a(8[ai]F`m4"4/6mYNyluqw8[Z{^9~!Br Y#h`E|x0z>xt1OIQnonou;kE2(*- ,ުe4ȴm08! #iBq8In=yC9G *}2|Yœgc"ʯ+YPPPPPPPPP8?)}Gb"h$WϮ6+WĘן 0f\k*6/;C?FR3TS Oz(Gg|Əv7qû^rj B6Z׵RZiqP?<*)O_+?ZH(~w V0Q 4??<}W/mon/U*Z˶MWMNDϩw y`xYo7N{yigZe7,I$xۃ'~F9mƃp6y$ 층_*50RV@@a2Yէ4i q qHE))%iILJJl: 8MeVT*ۦ陲Ԍ蜠VZ|i4M$}u'5>|8xWjkKUDz*JEt8&baZ^\ݹtiZs  $"y8-G8R5! .*]rڂ%q2_''hu?k?NŲ,@l>Kl< UF\`khzǹa<>Ϫ2e/qHűS-֚H&ȕ` 3"R*e:o{'HQ]zw\:BFRQd4D][uV@A*%XPPPPPPPPP s!/$gi9@eҧ #yx4΢hf{Fv׵sʹqU`:Sӌ>=`&Uֶ#L< ?޳wll5-rc"P28ZuUlk7~KW_{}ݱNSk֥W.i.v굚Tj,&d2qQxS;=6k׶.^Qu]>s-˚WZz<pI<LN<ȤB,V6kڦanl6Mƌ1kkRh?\V^ȣX&b̧1Enͭ ZC+gyO`/tzJ.躨B*3 C'$04ϣ0qUn\zW+4^89:Fd`I&i4Q|i6ʝV(W*^k6cH\^嫜% Ȑpz^g2{,yTFwkޝGXi0 Vvz4|!ΏL#H0;zYu6-_Ht/z0C*s~XƂ$Ĺc(.F߹WUZ}\\q=4",`0ΦhOgQ&520jJu˾Xv\x(\0L[J .^ ᣣ۷gN,*cJ/)֊3L)$m8Cp\;- R@D<˥VJ5b1WJzgTWaJIAT'&-wqϽTb c"_>KZjD.#W7k.S<>>Nzpgts+%v,JT*{+ls@FkhjZʔɕ4y|q?S ˮi6N^/UAe\YK6Vlh4+q۵ S#W 8eb> LeӲ{{G4*Ei<&y<,<]%h 08}p Tߑ[}e국O>_dAAAAAAAAA% @㎡t Ô0L|G2~坦m`yEDE<:l6Jfӭ6_l\~II1{vwYXh7'W@i FhЛ;wZERj (gYNGI >lqFXfTY_S`$㧇apeۀ@D 1IStfJ+"@dRFjɿ]-1K|~be? p< WwAx:?j\s0H0`VH3%K 8g1S\#˔re2IdF|gaZCuU5s%P$U,`2ML)皌?65XܹHxZh( CDJr6 'ߛL|2 }`|RvkZV/%ϳb+%T/ ^ *͇d4Tێ}V:&ȇ@lB$pI;vPD΢$ijN[ jd1@vB#H5""dB( y!Xv: ׷!6m>)FűB%:$5jtډ}3VOlLiqZh\kK"C`Hzyu 7J~usk[Z3)a> q78>`q;y[[Y*ZݘNx(1:Gr7/v&Dd(z}r|<@ڦQV/m\4[1$hdjzrCPY\RZ뾪esѕeZFH<( hAIjZ6z9\/4t!Y4&i$Kt[nb[ I6'~kYPP7/v?QdAAAAAAAAA\qEw^>biWKh< F[P-_$]P_XpҴ\vv/MeA/Ki;L.h'j~YY2McAVe߷|z!0d=$}KuL>a2n׮QYeJ+aRm,rAҬn̓홇C#O'oR2R}3˜2pDyI epC[ y紤s DK -DdQk5&!&̲з$@9t:fa&7 #,]bI"go2HG1 1ʅeĒ8P+`(^p ۲,I|OUlZUԦ h_5qx4Mãߛ=o܎c5M5*Z2#w>Z,~{o?>:'qM.Fwg{ksc1m4bVcgDJ= SX̒m9a;sb,uE0dl:),Sll6kU4 #ug,Kg-/`&Ūכ8 N؜1Bl։킂ff/+i+YPPPPPPPPPƋ&^ȏPpmAhZݸ].m0yF$JPṢY|u\ /$ M ,x<#mXmnXI%07iMZV/^ @$c0ZG18X" 18<4iҥw2:J!"cLpAZ3d>tiGpaRV}l(A,e4I,VVu8j dLq"JeRe,;Ő!YhY\a$AL"FRi$42()$qqς]۪Uo};ɒZmc]xfTr=ߴ%`*Ne&!c:4]c5Ϟ?:[oZdG\Ɉ/.\ʼpjӣ?(J2Dq׻W.mlt2! .D$vSJq-ȱYJdY*ILw,pYƮ+zݱmhg홥 27_3Pzkj"Đ Xf$u| _e{[?UdAAAAAAAAA1;D7kmQ2;k_,@1&S˾E5r_V88&Ss}V{A+_$A"hY&P y~Y_o*2Z6Tb(\wg{R-~$2Mg_h`eRVFZuֺjŭV]5 继gܩI+$F$!qZKܥ!c*iԶq%S93(SP=?u7<; FZ;Z09C4`gZJ-kylVh[ȭDJ(PQ:Fq76ʭk۰t(sW#?]N՗ D`ǂNZJ%`@``pxIϟ_- >_xx-jXɂ<|(=Ĺu)1LA*?cڶW)iҵV=S4+ʌ&ͣT3J[[ݍkW.,ZeifYEi%iHQET2 #)%QII04RD`r V)xAzLxIej>_,|.p\wkkT tgBP#)@F2M^J&iՊ{eHϔ$Z  ˠ2D0"HdA5hm6\2-:`Bܺڍjt,sr9x4͟=;;N[jikFU-=vMΏ /E/!qQ\0aoIo:>R]^u˫C}p0ݛw=x4h Z\ٹ~JV C)sa1)AyYZ6W6J3ScL0-LS ( 惓(v׫7Jcr> 绛>KܯB3gx4 '(ow*g ,4G,Bn o+O_ѝv?׆5b98TfD/r4\nm4ʶc ( `峫?3%M H$08 2N~o6FA8mw-bQ?89s ii?;Ydf*clviX^]u\2DZl!GNKM r \QV{WA8}qIf$M$]@I۷߱nWU˶jJ-aԫrwulsI*5,MDTJLg`@˴*yaZ5Y\o I$Ð4KMisqlxȍL)X,tq|4ZZ+saI T+F4zs:|<>+׮muNsl6[x^}uSqXDި??GѳzYhkFe}YygժBwN5<.kb$r8s s?$C::>xxpFLIET^kuw.w0d&ΥORJ3l[U]3+ŠlOخ8șd<i$iVU<3Zڋ?<;&d4 LV*vl[X>⍫od ͑^+ Y|_՗/?_,cAAAAAAAAA*81" "%|7K8J lП:%3̴eZ3ڕJ^qܒ_ᜃLk""c" "B ͆e[h2LgQ0͵x[ XZ+jlc!cS,ZWQ 0-uV`H4|dQ$yQF tLa֫zZV]4jncV*la,DKNV-K)90Lӌ!(+mL5* J#%5["#51IHJ Chpe<`@PNFLJ,vJ,K c!|qQLqF SZ?yɓ۵67kKn4e3;fVrl1 M:_`덞zJm2LӱN^:zڎiX9JΔiPj .]bqsX,?45Lvv|s2M4m6MôL08pE8/G}1&0M-d2daowgWw?kfŴTr:Q!.+%χ@҄a&DYٳ,;[c̷իkF-G#2eKdæXdP1aHѶ$%lY$`e6m3=3Uwu[s=~?~efd̷իAU!_oWsΗ:l1:HDpn-Sp8}t QoCD˄`fЊq&sÌ5ķȇOo=?AFi??=>tP[}ޝnqt6磣:s_I|2?>>~rx4?A~:Z~p8y^n9l z`x @U'dz}GFEY9lVݻZg93jc}zPn7Nj!**|n5FC+ed &q9h|ϻwփP)|w>HJcQrz,hvx 4YKA"_\_$An | /4w0U"E5%8obx6q!rh6/fQ\שAAknw{VP ]?pܚӨm-ϊ(G''|y̨LIIbG"KwMsUw*ު޵՞68?> G<7k8leEkV^ZQ_ ܂~h4TF /9_ ­ kqgҥk Rh(K hX"ŧϧI fu\` ^h5r@cXe_9kLTk٠ㆋ8>4/ kaAi8oΏ /?A=dnΦGm1ĤHAYZfiVE^8Ԙ2Ny4(,4+[ KURyOٴ_3d եMHVk}odzh:d2=z6VV{权~5fW5j Ŏw_/_,r;_OOfÏ> @[?V}=WU???;Na9CՄ庙a Qj ꝭLH7:TA)?~4؟0 /^ӳ?ߖ 5n ExsyMdr-RIdb>v͠P[D\C )Gc:-TeQcVeYjfOp ЍV,8]̊EloĹ7qE?KjTs]27I<l1O~xӬn ! yf4P|Bxg8nZm!IŬ8;[' akը,\40*mURNTES^?&;Fmtڍ~8^i8^d8h<i2Nd{5Ok~Ãvv:zw|0qY)K/`f˖rO_~B֖)ѳO<=3Drgggt?/~SOnV (+ NgNj$.h:Lʲ-Zky\W F^,q eyux%^lhV37Nn0Ҥ d0xr|xW zV7f(RcsPYzg>,ʲzt7UER&.wB r)Vfk .CD@@$c1Zdq|&%GZ%b& kaV 8eEn4w|' fnu{ƽ>Jt4y~vz<8K,0SˏFnvtjw{A7kuu5h k֯+)uϞ>|syn;80,CԨQmvs=ЎEb ,0:=ͦwt{{Am^m6,3&I9$N&i9pZ@]> +JGASu1~x#%`6?OW$ _;w )xXPT," X@dv T3R7*+q00!0$E+GE ,O^2L'YtYn5@' |>Obe4@Zz2\*ުw?=|:Iѳq $BYϞJ]uغ!u%Gf&Gm?@RD^&7[t{em;ysqNeYW\Z,sfK–V: RH3UO@& u4.+k|X)] 5* zD`6 J9gOgd:^qNYc:swO[Y\OKb[|>%)$l!{vs~s߬azGt"J&h:/$&"Z>hŵi?whk4[^ kq) 5#QYvr6}gkJZÇۍ^ kyVx]ja*RkâU+ PqK(*'d?MvA^m7^%{W/ u)D,'آ֎Exbd8J?|AݬAﴻf^k7[q 4˳<'t:-x6.E4G?< ÃݎpF, {Nzwɧ?{l4dc=?l^j HiZk:NCX*"6RQt={:Ssz^; ^i+|A LOOgSa 5USy@+pʦ 6뿵Z7Mk'NEqIAݬ3/WmO 1 䭶am]@j$pnfitQ,kZA20dYIZ J)hԹEϣr<"xxhJ,LZM#V)*28Xy` Ե]CGVM*ۏ/ōzq W_yG|qg%0PKh̃mYEQX"FDV`@: S,C`%C}@D"/>?r'AU:#_v|nD˔gf>_Lɓǧ{Z8)̤)J2lh2Ϧdz2y|$z o7BM$AY"D\ k+]Hl2",MaAE"N)x2ˣ"-@ `A:\Dhi%)] `D @5- Y$Kܵ3K?30"Xf\5-dQfrkV̀`+kհyۉPՄo JeY4EAdR|WU7>ZThj-E̩ϴҎBT8g$5yZo=w:5AJs#|!w!*J8;͞|>Lfyp~pad d/4(5Qj;iZd7ˍ›˄BBD"BEa;+2J4/ k^Njtu:a1jG;C"ϲ(J{UZ;SUԮQaie8Z+::E-PE^J5lQi5q!3N'bMi[ϙVq>W3=ա3ZKY6<}6=^FZ,JNҢ(mEi^3/1NYf8Op6Otrrrg(i4^^oԃVV;E|%qh1|1O4ҸH-[_;Ͱ>ۻ3uv'Z2`A*ͺnֹ!υZ}he-!8Qlx:JcxZ 5:\]Aގ6ZItz2L֚V+<zݺVPn/%B"2"&t4~@t p5_c_xƒٽnS& :ʿ{/_sM*7vr*O""FTa$Ēke@i>(="˳Y9cH[Dd*EdjKcIFQN^NG@1؍2,ӳ[9_k+  G|5[:`m- {?i&w [ZF=B$0l60tNkfA(JeX0<<IjAK%]-/%/ PEzr2eY4ABxCF'GV)D^м! {#-'a/?eign 8toko}{NgO} ~WRUj#C ,,@$  $ΌQӭfHi\,<yl:Ɩ/<*EP̌lcݻzx8;in""Xc-QE'C6n5=kZFsA﹎yJuTZ)5i5?|>~R#TkT ҃[SZQtIfX;,P; 4+T\6T|ƧϢ"ã;f!+WZ@(b StEfxhQw}_#a!&p5h Jat+6%5QM:_ o_?qr6p_Xb% v~_y:~lNG& [GX١]%2p7NXt Cf< 4CEEnΆc$KzvSxzD.% XjM֖Ḥ[;Z[cD}plO>,ɛ"O)s|D YӟeG VRtP/샇;"jik91ei.&>y 9.[[E4{5QۡNM1Ѻ[dֺ^1cPg<[ӹ}|8|f~!1,̤SyXoeLR{:L<̲vk5uY`Y4FE:/NY4s$ :nd%Xv-P;Ji}&M>kX0 ׅUA?3Sa,*φɈ˲ھ`U4¾瘷> -Cd>.(˓F}pwmz¥Ͳ躚"VfWV독7+=XTGA2O?;oOΦQw~Ʒ$>  »Ƌs!/4w8Wy b !*GibkatEez'O$$PrK*bJ)cRV)l ɲA"BFfz_=yOK:c-e\ @!`ЬR,ΦNXhwGy^9_e%.i hi2tHwyw*rۆ9!`PZ*A4EzfRAEZ=L'vNDY5粅OtXcAlz|rEƹ->> kAs="b,O<2rY?{m4T iUUm-g?N8J:{;|D & L*0͊ph1O`[jt"e19dzhN4Y6U:E.KȌt>gh6Yyӟ4/|gd Bւڝ^5;{xg.˥.)@E,Kk/VJ[kѐQ9."5 H!ߠ}G4ƔcJS)(PvT53Q$Ibʢ4 6h~G)TO&=h 2g,Ͳ(/^q05((s qeUeHO㓉V6xi{Ai#+B \ Z•v|uYbAAAKǫlC %KHt7Kˌ5`K'90Y@fdٔ, "!0ZQJYcUM!"w[Ts_~G|G~Ϣ8֊| }u0;f{{fV°b[p9XkKxȎ"ekʒs0VKXRiZ䄈nǐa1ٖIZD=7Jb,8-KSYYzZcy-2 kyc=gE<φd<N&! x;^_~T! |GkBd6\6M0gEa{-WK:7ۯҹ;t:K󈑏zwap2t o\Wfc(;>MQ䮯?{݆i<5JUv+bYV^Y`4xiZqjðnqĩ5VUfz.Q\׳6?5?qdvn ]W9ک׃N\}{u 2lL,-"?xp6gQ;(JuJ#cp8omx&Q%sU%* šš˵4iZ9FQYdas䗶knF^8y:0 # ea҄gO){^| EՌ6兝Lgn6jEKC'^k:?*bZuXI2 yk+A"o   ˡ/~ً=2.#篆È ÞW+ k /lO"X,Z2֐&4f xSp1YgQ_uA.!"ThQ%h" \df\ $B`s,Zfk-dBm%dFf|iNWk5CD4Y~r<;TYd&cؖs]s$KfyQ2n4IqQl1OZjցM`ҺihQy[yɵCZ;qxnJ!3aMxddJ@{nR1ر*dFdIRi<4[Gu7`(t2EZpxv0ԛ v./?; hGR.20wqel1MlhZyp]m~|Wt`"UX/p::%IҨ}xmhFdM+ªps2T;!   *^xi2%U1/]FɁ@AXSn;gqǐ%̋,7El-eJsbH4EgSS 6l=c&b@"񪺖fZDDDћsvK2zYKJt[F@T:z#)e@ŀ Ir\ YQy8J<+*6۝hkl:3Ssݣ~l &w=]4vaMyTLY0^ICUSʺHU īT;,KHsz<.y[G p0~ʹ /-&i||v;5|]UEeܝF6hgl8ZLfE+eZA/byy 1ӽ卿_ƕm8UE^]t!Px^?Բw lW$k-Rea̎Xk]׍$MYTE`BDVDĀ dʵ .}xq\^a&Yi+FW H$(|;:7!6ls_dĦݪkdž8LĈHdI+sզW,MI4$iT?<4@_ zhK &p=~zG{u*3qSZ0Fi1&ϟyA4uɴەTS9m%DG\F,0ە4BU^S&K82"MLHEG^-yDP2#yPU.sfE1.Fx4E£^h}f -yiH X6$:9i8{v)U.omk /hl[ZLb>'h:MFEd {^6!kMJ*q\MJU6@M6nvv7289NyQff"V@<Յռ̔<beuoRS$HAAAAxIگ5}; or%\ Z/7-Zd$jh#* #kT[`-XJ(]L$p8t\G+V4NiHOO wP5yN"[vBf5tQp^fѠ+I,r''8bYrHR*V"׹%Q|:yi߹ s_yQV?/0uagpk4+kxk^?d@ka<&p8QAZ}f:;.E\gde4;~^ht\t\i6VUvK| t8. כNЮb*=zcuͻ/w !RAAA^9Wint0;)*ʬ0e qT$., Wb l,. (JcJXDiI@Uk @dq\<M7>'~Gѧ}߻ :Z,K)ݞr:0pgh:ɏZo||nEw~<^4yOyBߖY:lYitH㺫P. q1>{ k_._8Ghx:qԃ^V =hg-quUejj^Ex<$e^6~۩y"_w4\v `,.b4ON6 Z+\fU|ԎZ^/&]ƚ&$+m bMLDKovJ#U'k:1cZ[o}$tlAAAAxU",|܅l.d)/vC4%;Yb˲?>y:3ǡ^ۿwnȓ$Z8q]u3kg@ǥ5"T~.F^m4XW_Vk~H˦MAAAA $l_iJSLh&]o+,2HceY42YZXCEDc84Y#c(~h4(YE~6~=G%4k6_~ ~{?=y2dfYBMi]%XAAA/8rmJ-b.pCY5qWEf2Pp6*4gEf\ú)m$<n 4eh|'35qO'pо{m4\E\IiV jUH/Xs%&ҹp|˥jURD bB$B$&5q&m[$s)#W$B~iB"0"B   AekR!Wo/SM}+_*+M5 .qT֘0ӹV.Ẓ84ϬsQ+Bd"&BS41%Zڼ(fyќN0,q]r6=HiWX( w[\EB:[b3&I>:^{0贚,*Ktݡ^7$@PXE09{>LZ{ãz-TԺoJvȃ[wGDv" ňjLF<L%1\|[2 )   —/'u,UҨ[ ,u)MA` `ZcJ#Ҏ>lo+on@Pv[!!G!TI[lO4Mc/Px!b@_ŽDH*KE鳧8$:ݰ@^ _v"UFDc0͞>ͣ,N^(]$cjhXT$)NG㧳, <4 qxl.7J`eϪmDR@Tia)RLH GXe;VCkּbc榉SAAA^*,4"oxY;*FqtW~P׾"Eڸ"+5@]΂:tEf`[3"D; h-XVfm=7hܞǺ{慪*IRϟ?%s~0փתµ@v>Ȍ$V)ux4LqTA[»ˌgČ %|.f1 ^YD 勵鐸Dyzr6,Up_Rl㲞Wv]en&-jv\W 1n3"]nW=)B\c-9/t2Β$ 0|Elp+ns"ٹ"X'd'CB`ohTk m~pUi40CYh2N]=j&˓⪓#"a*VEdUj& 1"R\װe䋋İ}rGvMrY yEdA   « u]ԧޤI-6I1WXjlTH`FV-U)ei'M&Q>`px}h XzمU";;GpZu?D Ze6'|lݔq+p V2-l)nnp    rg#_>nAֲnqץxWsĭos/3r //2q.RЬMHG@cf$Ѐ e_eb%U9NeP+s)} V%U:^8nre)r׹۲fBm^k xn7";nK؍8kv Eӻ1 Uj5w:ut[@k~q ꟹewnZ1F)3f+7қybxށ/G    [H.e1 rx+lxU 7_^: |U^ /-wJI![7   kBT7Żߎp*F3v W͗oQ%v4VK,%CAAAWDT7Ȼٵ{ݙAv}ZMp߽ގ1p\/ex e8*kMϷmˑ[-˥oG]!/ !VAAA^ B)v0#|56[\7#Nj^&7ZQY)7 y|4_<2n79FsfN"A   BTH5Uo1    o|F=0wFK/Ԭ,eE|znׄmUboR{/[,R    _$ŀ s^wt߀ 4޺ZR5V7?o:#ǭEn8q=MdnnxQ^9"   K"_U0]f^%o;nDoC_DAAA$U*}>MeG%|ww.P$HAAAAxY$mf_UB׶@d!m^|[i|4;#tCïvcAAA/B~ 9j5̀F39קր/;n_t   pk"pw+     _\H8y׾ e[KV/,6Oޮ]W%Ă   rMW _a/$bY}uv^|&__.;t|c~&B޵_Z _\w *D0J.W*g/ǣ    lTe8~ioJM so}#7_ je#\w"{eFFWwd>o׽gk3#L   p;%o+H׻V"J4AAAAxMh$``;L,%N7ʻ½;[q-¿a[e%L)   5h$JT>oM 3T׫"LĭCw~]/T" /NǪ Z[/߽[Xz̚qse_!_?`fR7/7"5/ܷ=oy^,kw\,ܺ_/~F]<_H|MO    |hHiÝGmrr2nU sNěKR!_Ѷ/r6E^W׿\[[GViZ[n AI<*87^Y0j|ο7Zʗ+Děw7ع+.]ʑ_yUdv   B40 la5 0­qƖ<#Yki\:k; ._犻&mp;NGޥXzvhF;dZ%K67k>;RMEӌ­w_t*䮵 ]o2`a+o$CHz$*e{HɆke7_zܮmv}Gk\DRAAAn"m,#WBF ,trq.BUJAEw׃[wW_Bke5㵫eiE6UVpiŽ#yL{a7NDNo\w;k<;So yś>w7 ]3Kw>|Qݺzw?D\.\+DAAAnyDLIENDB`cyclopts-3.9.0/assets/social-preview.png000066400000000000000000004311161475451620500203430ustar00rootroot00000000000000PNG  IHDRLBsRGBYiTXtXML:com.adobe.xmp 1 ^0IDATxw\es[lzo RE4E:HEEA[5^7N{qg6[M3J;sΝ<̚' N00000000         000000000         000000000         000000000         000000000        000000000         000000000         000000000         000000000         000000000O̚'q"a$DD"DDDL$L}$LL]ww돐-;G}%H~A] ={nG/Y!3qn]HTSngFm?ļפI=cK$݌AwOA,B`$Zzܺ>M?Ү{RUۓU %@%[jO^{Kn}v1bݷۭs{/X{qif wL6tz{>{ߤbدrm+wuDŽ>^ugS>eo麓8,\^w2 cPu=t&' ;ΞDoM5+mZ]z֓ƞ{]ou= @o~6=w]/y`9<'S;xoӺ+U튗#x3^ɽ7?xSP+wk'/_A7—)G@q+޺^?wۦ_6?3ؓAn=y+=}Ăgo@,x]|z(bOz<@l%9;6ϟU?}K_hS\3k=^fҽo^2ۦ7nhp> ^owױ~~GCH~>7fy^*fkYD_La$""&cWZ${-%BEk抨 Y(~+ړtV½NJ`;yvq^\ ⍙bܵQl0]]94LW1eM&l?Gĕޓsb"b u3"oAZ`"".Խ-o_8xC4x͒}V~_eLgkL&6vW)֛XH&Q+ )Sߙt>%~zojbK~nxi{GgM/Mxֱb2 bX13b2 Z ueoM8t?GQ `XO <ԙ"F~4@x/M׿oǫEkfw+To KSD/M*92G"{[@gc>8orZ^ /~oq7[ba&&d)e^# `jڢIkEѻO ڇu!=90lj(tnSg{`)̛M3]ؘjWD͂|ޯeW ^A _ݦ H[ADbԖ7ꑖIŌ$ we*nXTk{oֲ5_)!ow+!,72ۚ[_dY)!&agu7Qy D5-г^_^ æ _=~>Y]G`E 2=bf&ZC ?L54`4׬{Qt[0i&!EIlBuN"!f&n/ WnLL5+<{tEpqZ5h{'06[r ÷+~Wvf 4` }{7r`R,jN+DBdmM$&o=[ϪK}koME^T)jEX ]䘹+<7$z; כoƎvJNW]"̃ʴş ,P"VQ&VJzG7ןE{~ < :zFčy|\O,,"N\q֛iB"ffE23/_},w]nb~ld$O0**Ms̠OTED?V6+2$Dݻsu Fo zCD7w2| "]/R~ `CؘiF,VIPX񨿒|vY&/"'X]9@`H-=ninЙirz@F\ 7jPvx#/_ȷQnSKZ˃.J᯼qaȨ~Ҡu.j/ lx4Nr@. MLjS&&RI3e 31JދMRGdnY-ʾ_} V,OA] PP#4D" _Yn̬f[e6",SdQ޾ f~w~{w<` P6N}*&-^Q}y/uT=N0XXg:tSK!'ezg#l[O`P'}Zl>&[G+36beشTfR~~V~6 F@MK>fb2H|]uߝi9TBF~)>_n2sT_2@3kQPd7u [wW:\k}Ž3,SvVvzÕ0Eq]=ša +"8[!f6KFW1v]hϐ2 dSnk,:62%RqqCDH4"]$;O@jrb,:;b!5R+Z{b=MK$݌))7}^Yw,(mswfQ3v43 Amt]׮։&krWD*k]w"6(Q:2{wa-LL"Bl]dIb;Qٴ-Z"EEkV6HGsD T?5iHK |0$k `Vo[Fb>%giR=d,DIwK)!`b{p1$6 ϖ>A2'F@Ѡ=R$̤kmLYbD,bf+Ell0YZf\ϊ=b("c]Šfz1,fb2 1X,܍-Ŷi66Vc[!nɆOa!&}>3/ o>[v_uVz e,%i}1y;:*XW\&כ:WoͦOFJl* 7D7Rl2kijRĊXn n+^fng7 S6+QRl(2 6M,L-7>}l6dB){׵=fLMPPoB o'w׹_}u) X2+( C-أIe)60M3eLSY&&Ad)&Eyޮ$ ,,?~ 2,mL*=^0>L BL*;+b(6Megd|6&D6Ť^/MQ"柁k0OhER7"w_id(FKsWM[nwB#oA\m{E1f^KeF7'EDl^S)3el6ʄ"-}%6f%::֮&f*NcWˌhRIG"L2HjwID\-Knn}_wW`y~n58掼&4B,9wOSdYFOqoz5w>7e<[ounRoܱF}3) BDn#fИ+*EuEW,Y8og|l骕kd:0RZ 7u"pW&Z(Re1FJD%-=\M.EDmZGaC4000w%+l[v\1k/vI74!%q?3\h!O|MȴT&lhh]0w{:E /kmYpt؈H!C#677;|m/bh_=n{5Eތ+K%-.(-k2HduIX1wp!J9j"JX0+;zk {օGH RdYD(1u{c)IYh3O,^!ML7i].+GB0L&MheM8ˏu '֨Zfk,)[ҋIbĊ`޳37ܚI$B"EW\"GX\!bX  ##-8LI"}Ddl1?ğ)5 X\-_y_~9;*T`{Io?X| 26ܗy筭n&2 >eڄvvcFWK"*d9Jk]aWtYBZO^tE6gnWÜ lے^L8uv)B^4'-⮯}DlMy.CyOIhw!yū5*`ma ##C%)"^~oM~O'~[v%$\i͟.}:{*>C%xd_hayGIԒW8,ɈS2a}v\n8)wՌzN uv+ l 8`*A>EbY +fֺŊ6gg  x̚3E-&RTox9lVI",\SR\))5cWeoR0ol^cCVK>%˯ߋlV_[颿GxJy28M11b4|7!y^#VgWNR̹$̆ɶim`[%dV,w^^vS=z}wPї,_f,bv煉O?h>p:ClRS,[ִNۖ*~8d-{ϲͫgra~R{åpu:_qWrJS!7FNBel0)b,%b6lC:xxc'-e1C?tbCܵDrBiiYYLJy2ЮuU}'cDb4gA~mʾCYϚ_I͜͹?Šoi-zJ(-m%jŊQ/c-^[{r(/RlŁ>?/7ڛbJ1[V3[ɰ[RwK'u k=oUw~:˭('nF )aV^b,]9JrE\pwΥ\jWx-}TgL,D2;2;^|=qk<;wVW״-ّvDaiiiH1)E^0xldg|ۆ 6X0=*_⍁FlC,"1.I * lGXJ:]+ 3+aa0y/=/Oqr_aU7?;/_\w_{y͟W}|Hnkԍ)yQY.bָw_hg>fHENb""SiҜԛ+m]?bi;M1#$e\5 1QlެYV)kc/?6;o'ohѐD"ݜ<rVԿk;t+o>џgf(fW&k SdEzE O֮jP eC +*+K+EA6"!oZ{¶Wkl/h֮KKZSڕ kUy!enkgn<ݯ2H&X|ބB2 XZGL."֬ SXI]z/n+pѧi%^z+NxFyq]uǻ,!_+G|_~yȑ|_qؙH+O<68#fQhdDk(JRtW2IRvI#a=6$$g> vm[zY?=O5G%fV}wZx޲h[jV[]0}PɸNX<O;-Mի׮\zmC]}#'bĎR څ%UC Y6txiAiAAI^  `|6&rI49."ZDdHn$+ lXT"^7YW3%B:B吞*,(ױ`QJ–(C ٢͗y.3LHږ|.\jQP=.;qaKJiHpY†O_zuvTUe%W=:k֘i7V'٦_1ydqlIITa5VwNÎR ن .NNoo>}ywߦzYOᛋV.UbnD3hٹuѲ5K:9rDi,6ls;;9&A6?^~Γ&f5 g_xKϽln\N^we>ػ}̏N7ohauLDڛ/usTCGN'NU2tx/ &vhNgzm^"TU;?u{ݍԣSWn:I&h<OU׮\vmSSSGGG20 -# TVVTQQ:dhYEUAYeIYQAIa^(l۶}v"}>xӏi+M+j?tI}C[[[3Ɔ |kmyW VUWV(*.,+- ,bR$hץlѹd QOѤN$TĆk'Ə;c9t\'v>| 闣Tfa%zV7O'{^xY7^{=뀭?}gs;o-4}Ӂ8>w+y巿[G1kݷ4WZ}&^뤛ꮇCSrv(]O鼛;~fMը%&lyP8~v8n&D;c-mMukZWhoonlJĢN(P8%%%CKG:dhq( y@4M6 &$:[LIuxp7!3-ox7͝1"; >_)庎#iJT*f2T2ΈkZ?V:yqS5qp0h iGk Dd*(_ǓJ)J13]]7wwy:e]w1lۥTRR-N{B{Ws˃g_L\"yB$W7zM;ex&\pU?9|XgVyƙW6rq,{f(X9ݗߚQ5阽9nDUUtH9WՐM8S.;%\Rvt1 #ށR[2sON3T&H7467]zƖ斖榖h,rRBi﫨,VQRWV9tHeq0+, EB~iBB1x Kj"Ӱ^=zYS~adQ&2uÊJ~6Hkc&3g7ɎxkK{ݪeK^SS[SXO&>k!FO4j'8 ϶Y+E6Q):d01ab+_`>y#+I 8 ?W7DlQvz8¯ɿo! R/'1f"_iey[0!"w>^;Ӫ&V}ɢH,x;i/',SюNC)7d)2ҩSiYVIyi0? òRJ1+Jϊ/&g ?v]Wn26wT7\[ml]pICSckkk}m}g4IeJ$?(2x=j>kW{j[ᅰ00f7 x=_@JEzeJ^[dMD̦&&ŊMV6)K&ڢ+|>| 6GBS =Ya%ߦ0)"LW8L#?\Y_2Mh!e[_Girׯ+ tFnzfqK󢩶hk+y uo6]aӆa0k-"&5PY>b4ץn'|\qG90bT C6KK-Z^{սc͚Z_r/pO9d13}ypAm,V*Jd2'RT&6C@8 B>͆uy]!ɅcY/:w Bډw2t<]6^[vM]]m%Kdrfヲ|%!~VL}=# #/2huMĦZg\wB~4Ѵ*}+)b"r{͋;JX1Jexwfybh5edjJH[j:O=]x%g ъ"B]oIgD" &l-qhk^wݞv]M'{`g[ɣnJ^$0tБSF /)*/C~1Y+̢r5Ҕ@gnwn3ê*3 +[[8lxgS^GwcKڥLډ':ch<ՙI't*銰>B!;*()|/t]H$tI$%BHA$AӶ,m2 VfxïvI-'wM3֤u_ZB_?i)-ӏ<~S8p_qQ"J$=e>p;q~7c߶YRJ)6,bHXyx; 7^BPf N?XΛ< +WՍS#<ǻ\NJ=vNjç8 MF&YJ1+bEDFW#(fְ`o+Z潽xa㧍ptxէL2"Rtgkb# )>qDň!/˸n*ţx*Ohx"~7~;ypa?`eKq'Wm))I+XeÆT ^N֮XSbU ł/>99lwrzlX6\otd y /PEKq3'ږPZvɧx(_>4R !Ѯ(ʽU3hEb rt͜#9q38xawӜk~q*m&1+mۓ0 +qi:Z++ 9L5շ~g>b<`5+]~m5 MA_^afMr-_:T于hrJ^tŏc铆K -{19O~{ӝNцa{7]Ïڅػ-9n);!Ve&q3:tX*GɎv8u[J0p~ CJ)efRT,kookmial+ #H( o|mQWW`i-DCg2_,i6,{!!+QZOBX"{_{Zyyhwmg0qIK׆MBh`kf 9kޛp܍Ø[Bb5oݭ/˸q 1lS͜2u_PhWk%t1P(,9 Hxu$]QQfEv83GL~f۰MӴ,&RJ0 9K lhg*4PxcmeXEŅmd(D%.܌ۛګWYp{~|q2Gm9~׃wai&6J;r~㭿W}ԟ@A5|xo&SOV7?SM۾~de5a]Idr@]\sh!uNFɄٞjo9L3MG _0ϴ¢"f?/R_1L8X,Hu "ReBp0>eyEc-झtv'-X]MM0lm]4 0ABuum]g]KчY|,qq N g>'N?4Mc#)8Ol=g&NL_|<'T K--km7F5aڤ}gK*#$o6 x]`0,#,zr/)n㇍)<,e[eYLZd(L2RYɪ cf/pW^dRD6LaeJ7lO^yꍏ?Ya䑻{ /<nodQ~8iؐ𰑅|H7WY4 zL)f&1?\Qd;|7f]gKc3M%%k._΢.TaISw.[4udh`MVEsڟ{|Eu/8v"eE;NqqN&\ӦH e1x{bR\U VB&)RHX^}7^S5r贩[.l~9~~?miy!϶C$u3lyΟ9sqF y$[,r?_B.Ə13jfeʏJFTE"H(}6*xr6 ItWnb3ۂ,BՙT&pD=3)I%p&ɮ'A͂b_(d$h{,wRtƕ#Ah\2lte(H-u[kݸtѪ5>LfD""L[,մzusucsO?w/9;9NFu;N0jW[+{@N-N-&@FN68ۏ?4iCeU [i4Vϯ>>kO9rWQq'éޑ^n|1ū[or1acZO~"7|m]P{ ?ˋN:̐jv3њ$ťB;Zs Snp?IHoive[63 yKxzq͊5"ŰqvaIİtuꛚ,[ǟxgc8g+r=[/ыn ѻu;dtՉd:6wwX,d\l¢PW\TPRQ44G"@(!vZu{5pv$y$w:iq2gd*dtcSb_:hOF /iˏIlR%%v(kol^\ͤmC.[V>62[^kݛT_w0l򺓇 )LZQ^2zWu|pRL"à&tGg<K8nyG#A2#y!}yp0..eIkGlj=BRq87l['~xg_x/Oe &MeR Ս k?X_PitS]fU^Lo|8yCĽՖ̔L:kj;iyH:gn*ٻe3%}lY&9n#WN9M4r&l5u̖SFO?٤֢ȴ֎_gKV'Q+bv۪088D߉GLƿ<|uw{tTAatee>_sїsF($k.hLjw:-N}cb7k:"ѧ2tl 8Jj("57]wT`v*+O LE-mVr%澵_!lxkFkaQDE, )>'L$g|kng3L!HrUGM?Q7)/;#!E$(E^6˓JK.wMd㸉x#lkljim%cx,OUX(/6,RRY~϶M'.$:s{]b ֤TG}﫪>eWרT%kWy̪D\sǿ]S)2#’ʪ¢pyyYyEAyY ?/0-J% sCћ]6b/[Y۟> YS@IQ{}42`M7+ѧKz}k-xwiK'@Fdʼ~v'V>.LT֪OW] 7suH-Numc~hj9'1{mܣ)\_oFF&Z隩=vQBŠ /YZ\q\Bpj3սA,b-RJ֮$R%bʇ_~rE.{I͛5yg5g^7,Z0(JZ%Sd2q㊈Xi+2 6 ea( DJMl,hZ2Nh2O;;D4n&T*S>#/(*ȯ(,+)(/+,*>o٠+Bh&/7o%ڝ[tQL^Ӱ3!}XNI3w]o]a8nڦƖhgSkJryvqq^IqaCk뭶gqxo^\+/3X̽jDs#]SolMih}^ڎq} SA_;p0")6Ջ/[,sshЫ)D~)P~L[X'IɸuJ"]vRrWvTQ$N>uuuͮM-w_w[?{^z+0𿞿;U L5u54tmjmmE3vj,PPb1Le[f~Ȏ BP  }vgYEkŊD\quD3h*[[;Z::c흝!0s UeeiAAp"_,*|`Я|LJS%x{/bʔZ ?[qf%ܻuH)uM7^}񟮞:Gx"EhҕK,;gΒ+[JCguʎ032]xw~I(w~‘`0l;b?Ymg"`Vf1΢%2swQ\9 dž,K+\5oe{ŗQ,f+}ʮBƆtMC"Ȣ+OW-+>_HD)^;ԕV4!'=i))yQm^W˥l $#NLG;tCB^dN~/"h4m,5s_.eev½2pCsӭ`A?=+n?xY%ev,nID]byDDebauQ3+eLm_ %EEHAW\)+yA"m*V`!8;:;;;;bx2Ad"R%CGN2|ǝg⭉OIqŴ)mՂWWicﭕMH&ڶX~7qUEH;xa╍ dƶeEeq2vO(ìCOW%䖨~ )!n]&v--~wV9d'zi?.(0MśRݷ@XzqYR?lW3b#1hvZ5p㺭m5W.mn^1uTחϱ?gzgν%ڸP?pq?g]~ydm8DYZy`-,BzPqyJrq "&2 ömFfyI$? R\**`"^WbM3]t&rcDg{{[KkCsKmsg}}q?g'oҒʩFivf\aϽQF}םވL][Z>?>I[NFW뼢piUe(?`ZZt"ƣ?w>t~zQK0}`y3i>]ϼ2{n6lwfүN<n݌j0lF Ӹ亻 󢳎ow௕!Ч=bJH7ۅYe\]p}O&Ljx^^"ۨrkڶM}dkcղ.T}F8+0 17xd*)b;̚\To[R2hZh$sUBI$ӬXMR~S )`դ:VyA"A;l7ǗpUCA"6HHv]q5i!!ebRJ)0PsBdsq}+wOyO_G/k,X=waq\qR䳍<YYި1[l=qvnC***ˇ,bu3άD)ܚ]f=i }QyLrm_ﭙIm u6%DߓeZ&3CC~ }_"z/~zwŋ}2Mas]=)kZ0%^#\>EbaE!X㸮Wtܺ VD̢H(Glda't[͔_O?r}u먾% %Zy fD6,>|Ej]*aE{}U~^|v2 YRUtO/yϷuꊎ o+/^ި3 ʽ]Ig2y0n03gtZg)#inT(s8{ҳ);wߒ +>k{H1}mb /O|J6H-<ݵScDVLM7vU׿`C?c/^qcw>gzūj(RZBZK<~hAu3ުDuw@hrkTkw.k!--TgQe4]y5\VSoi'[frx' ktHVJ|U.}#/z6˳pg]55m)f7ex2&8xA$1rq9/_ 7 7H|mM߯<^[KBk:`}4|?IEHqK+w+ZY1zu߯w=_D.?q7?_JK6."Oj\diYEִu-_%)GSͳv| ljK ca[ܔ=h{Uo{<)C~2( ZȌRU6ko޵֤uFD(t5,oj`]/u=^~n'_hYuS* T^~hO޼˪rV[O,(3 f嵱s9ebM] ̊9Wh1-2,j=/{/Vἠ&'n 9J؋Kg:zO&K%{c&dZ:}/;vɔȨ('FViΤ9PR`?Xڿ=fAP%Z^Z(L +0fptY3K:{T%G|GgM-+|6I]v!VIiEQYwsS [ AJX1+[o5cYcld(aaDe:7e,Flڔ2}[~~W{kW^V;ܿ-66 3 ô-ӶL4 |`R)/f҉xTՒN?(MM- ~(b >6oҷ|//f"}sfDWɗyPCU-VjdүKƕ|]f]wS/ke2&ڔ숅|p t݄ab:P,%ɰ̂`6 3ƔlEѬ-L]G{K\Ґ|:JڈյG;c=F333qƐ| &\`6 #9 ny=}4wQ{<㇏DyV87dĆax-/-ؒT6Kot{qW_?=#&ěpZ0) ˲ s\b"Gq(!NhvNov .^tђdTʪYRI׶Dߜ.'$Sk|L[,$nG"YӬ"U}ʿۨ&=$Zڲ>ٙ}en'KTB:]œwE70BCCOѻD~Du`Ly`B9( TbBRD:WGi'wyѣAB{ V/[QYPmL.ܻغ7FŋހkЦnʫ_8 MLtyOOo#_b|4c;;K938=Г3fLSY^ |W3+4sƍ]wW,phm.Y nȋ:򢽏h#/ȋι5k2H'e?֪wo?Gpôm=s݀[Yrb8hamW^ IP &ÌEW4`qum40~]yE>dc]eUm3g1{-Ov4twb" F=-m&UzVU"DN&8jiH_}έ+/g+~aO1a;>7>cICEwU𴽬a~Zz M3#ɵ-n4)Iݙ/ҙt2?0m8yU6wNciԊ77^Vd4ajW_RH&IA6sY~5~VNk*>D7I'VҮ[N0Bpѯ;hBKeDX)^hʆg]Ӏ,2u'wL?[X׼I(\^62f>g=geˊP+59=N=4yKIȬoq`Ŭl?ҿ/Y^^3rȤƏ:uc^2TM8{/rG^DkZhu-+V/^k]|Uu_6]wXCs3?ym<9XI ?V_o's+i 9\ɫr2i)-T`3W4kMdO١-d(w"[n큟]zi7ٯMqgrG]aʶn9j\)#g1>owҖI&7497uT1)7oܱ>)':Ty{v<~-/+WdfHY?hZLbU%rjRɄ"+8dNJ7C Tz?94utΩW6tRRӫ7=yskSN6, &TFndOw2GF*v%?a~٧Z[їsUZ!af0PM&=k}h6ڴeWި=:ȷV6"J%E|E'č>tcw^|㵧p̾o3$b[7Lkq]q\q?{ -1#+m3~q:뉻.ykg?~{n;N+)ܫK_{k.a+WםxM_"xaj0PtЭPugX4 |N7P2q->$W m-d(R52y Wuiㇿ8¿]z }BMdN&k3Lq&2p]3RJ wO&B,NJrcsK:3oL`O&$)ץO'l_-^SkZp*lX]6Q#*2iWBPQF䷂e=ZZ>Zk^ MhGVzPjmKv_$engUM|w:װV/U7ڧ:TGIjTJHk8֢E 9))ZwF?/orr5wcysܦx%M. [kH37I\ݭYW'jfW4E{N^:O>oi/οW' n[g?ُ]_㶓 )!"'piqo= ~9w]d'rb 0F -yiWv3m/¥k5<202f֚vs]/և\tR`f}("yyd9$!h4CI$Qʮ4ygQ%L`i1ZubMYR$i S(͔n Xм1DCVjZR)IQ.mT\ch݊EsV?zȝvT2؞,9F,_G[X$iqՔq"yQv[V[He[O Yئv2 W4 ƒϭnd],!ɸ,D9ʬhK}SxYۻKX*_]"ݙ֤5%꒺ߌjm}[sS"}|@ܭ V"Zu_?ZL$BZ$=w6zEnϳy @4msr/}MUs>|#w4ad[[E64~)??;.z=|M/us?.[ \ -=4+ - o:Jvevܧn81,ԺFrDbqY|a_%s3h<&.Z?hT-)Ƥ3Z{x͉ڑ.J"݁{nVgow˭4IIGݨ . &7"io/`ɭF"40vmmy'ˆ,:ܞ7REeJ5mͥyἀh8N%+#Y}+o~߯ėֺuL~3P)frVϚ?mDRC(>5"s\1 S$%E+Bd(&fbr.{/Xz_2X RJkaY6;n}c{ t'փ_W4{ᄋuoDnp6Q X e`g-Y'_3o픗\#^}{n`>'ffFy`ݔo=>뤃"MƇ~K} `pmɊ smmf: |~{츪4i"VQjiwDȰ Lbr7&?QunMco,{{E[TKړ:!퐛mӻ.{Hzޤ,¤퐶 ;uGTwH?Y{U{sGBe3rĩGze?|貵-[XRjhj]UMѕ>%)uIXSoug<.n]%o/wίNnnxEy馎@A~(/_1g;s(;n=&0"0Q$RdKs}Pj̤Jju]&vtm RڨߜmgN2a~ewi[ )׽ٿ8xE'L8Igͷ*5o9m\CwM̼mwuj=Gs[' C ]5_ή^=H1cjZs7f6wY =tU}][{g ١[Om6^_XꢆXcK|X[ΰf#%gװaEl\V9IҔjv;S[<rNgӞGṳ68TxY ozV?vę룆|wW>Z+ GtƩ^"(ƔQT֩tFmGv,>mǗ}S-JQŒ蔛l㋶?[u۝i`I; $jZ[]X ] ǔ ?p6ijw(}H[T6!0B S"HD.9+VrWlU8#ծd%\}We$ u,%}`+w)Z,$Vbmaɣ;-vR; B_sN9$@POh ^XA"\vpLZdɣ[jRKmiqHk)R`W 쑛YqH)r^ێXbC?.??Ch+׬Ƣ̤nԻ7WOV.֠Fmn-mwܺ6iKCrvJ]s8xB$d;j_vb4TQ.(hooh^QVXq4)M$슦L{$] {^ | lrv^mXYMdIݐpDkd oTg7U슏{y>:n"aX[ӄl5ʷKBlȈ<, FaBE)H$ T2:.31J [`'KEhSW4g{`f0xTwM uZfxYZN_qPi߫utKZIh㚴)M}T,5D&fxqgN#GcM Ͻ3իmU0|dI^^`BW>"ך)ÙNs{+HS4Rs;3*);2,JQ&L> ڹtqWxbZ%5W'M$R:[Nyz oowZj[](T# FmhnfrBI$b?Յ֬|tSl~kqsk_]Ծ5+Ҫ[ q+rۏ-*7BFT:ٵH5XqtޜМ]W6x sTh-wJr,m&4uӛۄO4;/Jdu vZ1;=  d[^w]K[c הI6&"#;Ww˝ZQJ>{Ѯެ]f"2M3LtT6f˟}/N>]~8zPgt:]gRW*zݩ)'"ަ;4*'syQR;:aygaʔPmpPf3v)Вҝ0ILW^R1kjxD&꟟w!ț]a?ׄX8:5.\\Tuk|~cb q̹/V-iâUp6G=̛Xenlsݯz:$YVVel&^Zm›͒;SȖХSW[no}^{lw$"N:3lh]7wgiԉ#couM4ܕ~rMHnKzzd"Y0n糇Wl?h*sj3#M l_eOTC (#^w4eK{WZW&!N%5-KƖ6罚'>[ڼ=M.}nn.ڸ&H2{cuE-]|%-7I,ko %\ zݻt/3+ӸOlc øܟ F +sG^__^v1~YCWkc}kSlğSw_vwtҘ i]z-_tuIYdڤa~HRMMxJϚZxE5gG s:MOɿ_y}̎N<[ lJ CoԊ5q<"QJyDy[vXP|@}:5l|Տ'`ê&Md"g$`MerY^tdDY?mx# & -uJq8J,^m!KՅձfG0H {EY?;DIlt-T:u>䚐bfX⍿W@cܲ̆aOa]Z(ycݳ]cue~_F&ت!%vd6a_R'?j赗ݗ=wſn>wvSݼwګIlgϭ-;Xi113&gQfIcP"m]ǟ-9GT!^X}O>CI^IP2MeZ24ؐbt˴aoh P'r{=Ze([/14eG#'_鬩F':V?jZ2/ZZ&-9r֌~>[}aԪ5ng?so/Ty>Z6w7#D^U:mܳgW~> "G_91h 7Z)v5n4^=g:N"H$/HQ^n b #?k\F&aOGN(o=Oc ]WwM3'CfW12ߔ6\&&2XYb:K|z7u5/.9ˎ{6jiRF"7)ʉv}CC嫨[z皺z&hJiY>&؜Mt텭{4},ÕnO!;,72XeS&scvzžp *rp2U`eKĹ}_uܪw*;L9[V5^e oD5`L1ݽumtz۸EƏr_fIN*Xx#?b1[GÌIm-+SL1[=I6¥ VX?|9{~\)pi (݂ ER.:簳.\s÷>^~7}'^z_nU Kn{w-IS TJuD fv<>3/H)oq[f*5{o]*[ݹDcg-L,<|)0sV?~lmx?];|iPU&>k fsvI(]͈]LJ'&sIy2w^^w nݨ1J)f]*xӢ0fo(;gC᳟~v5zDn`-~_/ o,}W@]-jD$"ݷ )=7R:vK*TUQ9~T帊aGV3{n5_sW<6K.Q,]8֛ϲuzaW;gL6|+Ddm2Vb-5":RVFTY$gX"IF~87-H+7P+WJEv[T^xuC7=`ſ^wִ&Vze}񵏮zXus+DJ& +rX.G6iNp;峉.ã W\LvıbVUwi-;\ie|5aP5kӺzh _83}j}O2+׵kɨm;pv2 ސz|#ƕEll_uiZG_rgjV?z '38?y~yϸ/ 9WczϪ>3T_S/_upѽ7W`CK䃈_=I'omO,^RͮRl[acjfkۖI״w,ͤqEӆ;h +K,h_հnKsʆٟR{LRx] DJ Z)X9N![ҒXښZ|iO}wּ0g+sRxmm'#Cw6ikH~[U%35j,L^fI̓hs]kAޢ ք0ׇ?_r vïvmt#@?dgqn}>{6p/2 kV -k-twߐDU DFnj"/sD퍉?Gsm3qǽ"adO)ao`g2is/97yxh؎;pү.?|9{z{8vM^6z| e'0tS}=𤳏?_\xݑ']%Xw>U+x[2l^ˆ4ۅ['u7wwg3[tU:I bb7uQd-lv7Qd4/mY2+/?vQ(U2b-H#A*֦JiN]2Yhvĥ\+bb^T42 ҖU@2nt՜^i|e&::X6c*4r[]Yy^Weuӄ8;o>~);r tQlg[~~_C WABB+bI`+׫WJ n렆LϿ3yM/iwgT(Ӕ/=rq7FJEa׬hto=R&Y5O=N8ҙ_X9T.#hՎ]뽣v]u!H Sv8ϽsϮ?.ͯJĒY<:NrfZ]]eVMjnsYH/6eE `JCCGT(C&W7ּ>7]ۑhlo^Jg a J4YenkU_Īoo.&Άha”-ײݘ\ijj|oI+k[&3n폵|mem&ҹI.tz!X%.S9\| |s5dςz„=w2V[4 ڭ-fG"0qǟo=qdM- le-,Dakyuէ\q\]wx>lMzi4EE@7@Q`("RtN l:3~&TgMșٙ;w==on1Q#5i& d aB)((RJ)Ac3ιFAZ]O kr,Ӵ SSRB,{UwRhDhƍ33r(be[ WJBŤ’r{Qlm b"QG'FBuZu,e9NCqEޤ͇Nd2'Jh1}]UBC:U{^(;31 |Nc*;Ҋe s#VΑ'0@UTU쮖E݊Fy_Q,ٴi٥;/ CM _5_5_5_5_5Nl%@UkF EII*1 " eEJy47ϚKj7"N#ײW}Ҵ=dNaÞoa uC0&TRQV|С]ٺiIG2-e% 8cLAVim=b; .8 pn[ySm?4Z΁ҋM[4? Ъ P1"H#":A2Dtj V EfYRG4*Dݣ1:!u"IJY%v(8ţ+aOp MJ*Lf`N$V$#//s/&JlXH05_5_5_5_5J8/[=.U|8"={׫EiI[K !DvߪACbċ*RJhT\!8!QZ0u7I=l;pq4t2S8c&*ݻ}>o|>YMg_rեya(vȘi#B;|߈m0wi/O߿ !|Mq`)ss8tm3DPdMʰd@9y{0Sɷw%;,X-uU~$SkNQca-?x]bbb,$+V[RR\A./U'OUua8Ǽ:@(3Ɋ|M2 qvui<_W֤~L~^ةt/ OިQ,ɾ|||||jr_^Y3ԣ1v_{:S t(6Eި 7DRɽ%DEVϘ[ZУ{?,F8bA/j;xݮMGŅmXnuwY'kYxvU+rWS^{ܲl`c2Ѩvkؓ/5VG~'Bw*RtBXNSVmeIjJU,$֋g"!~Bx$Up?Db)e,pF5/:/6NjJoKKfibP5j t)ݙҨ`b)*Q>Tqjqw) JB,yI|||||oW;3dԶmоmřׯqm rLE,-ju-5FU,HI$a9A(~ۢYJ6{TxߢkR3Y瘐ҢjʻKv洴c }OYvmmژ۶+yxPg^c?/$1BCmyz|-C2 !j>!p2IcT?0zPBlݱIV3i-2*2duHH*HC6PMD9K+wYsr'KvX/"A]uL&K yqߴ:d>̑>?;o-}t ~xiބb_寯7( xbXylЗ^=OtuY'SL9G 9/#T3 v۰E(*Бs0o[2{luՎk< MwT<SAHKI?糵gRnr~-aa6)e2Rd$L*UyYzHA-e,5\e XLCǼט?M[_a+02/SI+(,gFQ`_ɩY&:\'6f?+dAˬI5S#ANu১AؠZsSsxZ%;mg d]pT ys+ΡJ򗹓%IGտݟՔ*e9vsey [7FTkkkkk>o j81V|rfjp!aNKӤVPPR!JjFWAC#Dv퇳r#?Bbv0pT]~9yfmmlh4*q`[qO92$Ѽ=4~rpRι͛=Аh\hdRqMp۹, AfKOI~#gS'Gܣ8}G7ʊwjmk0Ɣ* T-RjTT: iVM5*J`k }! CV=Dg*PJ=ś*Pu[~qj/ ܛN/cՌ*P!!ep "{«dU`=,:pbJ͚KB͹E Ǻ&^O×E7؀[8bDf|ĤSƄչĸ&#5±fVttMD ē9F&ibdĹlF÷l //ߋ<Ĩ2'ϳS8!n.#kENvM{|r/ ;)y~Xւ*EN@B"SDR :]d#Ip͊R74u‹9'A&"/? `^E7v)iAz~@{j1JOxD,|9yQ1q˺ Sܡ[LȁI9wUWVcrŖ쬂#"R"`Z< }9r}^Nf(*ĜWHT1Z,wZ\V&2@ڴ!,eYhG,."m}fUƯITֲCƁbX1/\)eކjh\/ z]mFiyϽ񀈅&ҰE>}#¯ X9'`~:ҽqM[~5|¿7CRWmػsɢ?C-:*a86[Ƕ3բШ^lͻke a0 Pg-%=7%-SRrJ.IO"Tip_Ę 7J^;>BӀ$3]H!8*H?}șSgRrK*Ȋt$B0DVV'>YZcbBjŅcQ5pI9w,)Sg23-V$ɲrYĂ ި^l u#cAVؿ#76002+5#/l䌃R'gf:\.H|`iՎjӤ~|: aaA-;v`_1x2@99.@|>N  e6yZ}غ~XHQ%;-&SJ[ٜn~ٜrrF֤~wޡqDX Qu` | 2F\ZMNӊz1-$ߝߝNGƭsJGH7Fko\a3u;%6a  EXc-ӝ8ZBcjL<8ƅDE~j1&@߿Ea=ǵoO965keKVJNJ1  z*s߰҅ZtM)cjTh0SnM+ غSJ\VJͥ9ۊJU划Y"XrC\1wo&FQ 41H/->,doV]+CQblm#1P-˩p.W /F̲"KtS576SlƼd\PXt/tkٲy]Eq_@pqM )ُ8dr{|7Kw >{AҞso|ms0F~#J)h/,g;<_}_+(ݶ~_~29 WvRe1~`̔8u&q*2ҳ o;' :א})b>(h/,?jb۾/2釵,j.<ï y^Wn.*RC^(!⎥sVAQH̟ԶoLփ7 qҶk# ~^99gmr5VEB[F~EJZyibR%Yi}N;#KT:ŤwJNiveyֿ^xh37%FEjvtZS0op.+ߚ8P-A`9B7 ~Y%jw=%[]Wu, 2^=;4e ҟsUs>\d 4Na28t:70E^Fʥ[3GWoPsV9xGԔ3>pEcV9=O"T-~?͘fzcըFmeF0yo>vl: }j-e3_Z{b@.̀qrm:v7߈?=R+4*B"+?7n?/`:=7@arRZX\ٷ+mlK 0!R({[6#wӣ(g(b@ϦLj颕[K-h0aȾ#ѪYo|s@W0 le9(9]lkL& F 0S AzQE0dt_8"$˼沒֝VZ\AVm?IԌw+1uRarڹM,/ƀ@#Āk\h,&\۽egrZF~p- ޢc: &ۼ/L6,79UJڿm KLRz<͕Yg}bHP^#w2sUDdzAf mKS`RTtƺRE.o;^v&[$E/Z׊PͩvZWN#L!0s켌ww"yGsYpQ pCQ | KCɍ\F`Bpzzç[}H}s 9>Tn])s歑s3g)^}or63ћ2R_z ysъ $&>ө9T%2@L`.{O>gFN\a;%94OzꣿwI _]1qyT%WR??h3e;NXKٝW8ە?9|OonjB)w ~빫E }~#aB eDzq^!v$ BA0Gs8`~(@^8T ,08sg KA# Dػ-;v'<.AM !3"$R Ƅ?}\VQ/%?.^q:A0&fGߚ'3?;mӓOgQpkw vkeuU&/˶Դۯ:>fـQNK;!)A!~>g'BŖWJRVe2"˄2A<'tE1:c/^8Fa_>MzlвFMՁyE`kq !L@"O\\,RrϞ9RmZՈAB|:;d#9Tkc뤹2p1E|nMto:QXByNgUHqI=|&0,[J7 *!q^TGJDAh5)%qIBJp nʐbWsRٲ]g2tff.Q~0!'i`ZP]Guk$AF U1 @1:?)#$s-Ů\A~BW&J^99፱Z% |7ܘm`ێ8K@;NƸKFASJ5u.Yo۰]j >䔣'jff0sOrju=gkr.kțmcr+iWm{G<NdߢLVmr&䱱cɾޝ/B ¾?5^Qlc1cLgJW!{k:[uEjzYqP0s#t+XWVt%3y惙i!rerN(ԫ%⬂)P!B!1fPr8\2K23rUQ$HʱHVҬiKJ`P%IܱVX@1%+ \ w*Rl/o=sv_<+wI62V*DFyt~^__Ι{QQ~Y[tL(w贗0M7>  ގ_aKƨ:ooEx. ЭmtA'9C2?0B>IHUBpƷvqٌn?j'oCXt:sS}Ւ(%uK[Fv#N}y6dd"[1Jؼ?ͼO_._IpOGPU\gw쑟T( qN֬[Tn oH;dE ?Ѡ;G\aiQ ؛td!b)9Bc `KÉGj&{}^"QXlg,@0pUŅESԵ똌&[cy2NpTjtnyE t_jo|<>IΞ͡N cJ7sxSwlӰcۆ7>bg|eo.׎Bt_{qXJGޛo񝂁֬wngu,+?[4|'Y$X@EaQ>+#:^+' Ł}&OucrU_,o߉R**4k8 (E+CJ5 sMw5qF^pKEE6T*N01aଢ)8 yy;mڭpX2˼[~y!(6].@F&2*Ҡ~$ bž#90d;`+GgX̤eq4p) d2Lαvb._{#`9 Dfq$HPsS@nk |1v\ϗ:ɭ!:v*cFݩumޤHRunqnAɼʂfyB[ wC1"O~wZ`& ;m.iV7.:Ԡܽ?Jo8xM aQB J#:nпw|lhԈ{{.}l(!(xƌoV2{Ƹ/\g{0:|k_ a50qq׿m6nP&#)}uñV+rr>ƾɽl0TDcDPqu;O.?܄zjӋH\?}w[CF:c!A`2]̴g{%gť!5HuTgImҕyRfL-r&Xr?T+ I{WKR=їGŅ19ct̜‹ ٿL?S0Ȏ<%vX!V rqJ]y3@Dz|P|t3yizr͗ nB%^ҧ6_W]fqԳyS?e#ݟynw\~V)#@_0ƸliyGFM-(ݽd^{sX+.la!wDSQظ`{ڴaSՍ!`_&Xx_vTMj7_VdHXs9W&Ɋ̒w,)бԤSgL\{dpy:Ԕl+kjDž6o*m5ɴZ3)wo.BH>/x}g 25Lmײ^u׏7U"&bZ cH'>s虓gKo^{V$}:> E*)o?-+,uǫC &Iu>m S2c|w P O{eӯػypQ}VG>ɟp5Ǚ eri|_U5,E""" gwuQ\dukMn1;gKƏye%2*nѾuG~Rv>r$ӥR !# P]8R"KiW \Wp3>HU/Ql Q42V푨:S- \ϥsf~|A<զv 6*NÄK)q)YgsCxͪiӋ)S?[0<{ C]qU۸qkd]+$now[05)38_9 ώ(;]5QMyfo虱o oпcN%FF% +8B0BsRJǗ~XuR|ǬOըě"|>w#z}zӡU:u#L&n+θ[6ƽ?i?K\OjݬABB\ڠE\ a 8->u&kxR恨Es^оM\y}m]ۡLD+ɵMjxGՊjΘ[G_~@#(qXIgvHu]OMd'?6ռ|7<\(VGyn !ڰҔ_+V7LKGȫt/KIr|l2)<&6.,B CЛ@aTQK~D ڤW<r:\imܴfݺ繈ؐ'IjvDDK7QĢ@mOpCOyK+ r+d0Ƹ0Yf̹eIv:]vd(2n+rĽ[G:K&n?DCYL[18܊C7y<)I1.V1ۆsE_/p sw;_ T;ҲYoUSQ||ho{9הk~^%!Tr11w[~i{¬>bm0c޵3>y>=ZG*,۝$ˊc8cLQ,ɲ%۝~F]_~恍K>ZG 7\[O0~Z}w6?}wFu"_}m?YתyANᒥgxdS~ݘ!wnkg|6_}:gG_j^=;|L~ۢI{ 0쁻ԋ*ksa3Oˊ"dᔝRX_M_,3-+h\+}?tTxPMYD1./k}+[7; |׏RE5ؐ%6(Цk=9u 'wjTz&Q6W _dhܢvݱreC$>a2١OmF̶%DO #7q`Ǧuyڝߙ~u4P=Y^ncO*JPE(ŧKc|P=I0`}ßފ E:-8rW7K~VJc%xшP(V,UFXO ~~*jD cr1YJM./K~lG֊ÂKgFsv95_ ~:,vIGS8,h1ՅG DާY+#3h3iy"0ɿZNT*#RrnCD^ YEUӲ[SU A7"M=|7p5"9^z[/Zifue{t{39z_ SQMz7oxқo䎨JX|g\Gq{ZMzt&ANs/خ1㞺ƣ5?LEaᒍc^v]Dopѡ MX%oqdw+78r#tۏ{r"5>ąK6>6+o٤KOw%ݼt}DD ?z5wц:nP'ߨ{~yٜۏ8rIWX1Fy%Ǜ3"&*:;#wlҶEV3U>~V4굯<3t2H{ONd]]}Xۢ@Dh_/ٕ"t\Uԏ풬j(8#鄥G<[(2D.Xg;ך CF 7꘤B}'XtF-~;JgǑAMJ+c3O`?Ȋ|kj1Ƙݱbɟ-ĔbZ {sCl0鹢܊Y]>2%?/t'iײފ 򻹣RR^َlqys?Ovp#51Eo.js 5qӎ#;ԍs;4ᒢ[30(\o+r;_?ѥ,">\㹻!0^qrL0!"g,_kCNݡcrv?v\zlfc~!P A;Ի2r =x|ͅDŽ%*=d+B2Z@zk+t9Rr Sկ1,'ΤnP}daZM#O$^ˁ (̊r +P24gj=)"%ݞ#y*J\{֬߹wkỳ1\ SngI+JZp&H٥ҷwJ U*~~":GwL@ ! K0xu$tvN1 !FtʊTʴx[\~eBg9+_})gs1W;ZQ*1ߍ)J6 kc)m>j0Q0"x]Y^Q[\\uOqYu;\+ٹ骟ɁZZvJb[seEvɭZ$: 0zz)9TՑO(IKWׁ~lI{8 ;]-cc.Itnk4Q WX/9єI)IMy׊~ CuYG4 ^pwlƵ~l^I ֓uJ-eEdYVNx$}+ѱSivҾIEs^ܡv$cr;4{vp U`~*GBBBEB:'S y UQ+; C$L#RR)RV.*Io4]\AoڽR[v0PhО(ĨWYpAдvL%%4NUAKw3ƪ/90@UӦabV8C(s5[i[nG%i;/Q!0YV8q1bP# \ieN-;5'w\}ɎrW6u*X]mo!!&A: {s`qB2Q!*4s|؝d~F/c\kPBr7xtඒ sD[:^kOuE^"Z5IkuF/isLQqiqٷ?f჻c һK^߿}U:u{R!8'txFv o*-UapI/͉k:084JZq8ttoW`?;%dݲw/WtDм/'4jXo_Mt6o=>˭|>\3PT @ !mtwsYH:x8c7u?YH4zs #^B\AUP]\EpB56,qG zW]Fu V)\Y]LK/>(hH@5Ǖ.?BNv= 2𒗰}Vg-6G{0s6BQVm2NK. 9$dKD[wm+~&c<>gθxgjWa!R̘[!9 #6 eťrdsZ6sVdeY6 j+M#B:'t` !lP@5sAAHAq=!&ܓ v CY1|6Sx]| --f ja~#_{pcT\R^a{cՂޢ8ڴkY]\j"ʿ=qNZ4}uzDtRjTIOtۙ R ֠{:xࡓgh=?]f5wo6nJdfEQؠ~\^M-;}6{)Jf^vMįΘ}mNWB\/'kO|mn"!r9\9ՋLhڤ%9OG>9/  xp}$*:%cI/g0Oz򱯟3g`[zøGOjGB$\W qY.X^ ĘXm?Niԯ8/+5_^z]:urĜN Dhl0!eإ«s90.#޸Nƀ Zbc$;x$6v?6 d`Qml1תHT$sԳclgO^2I{naE~AIj;>s{ݭ4UDlH{[֐Y߼q;bÝj"%$˲~# $G!.w)A)fbXmyNNCSӝ1c:F 2qJ]`!D@.d K#|p PUt!VoȽsN)?Vo0p2EfK+ޠ2?OUxodYfZQ1 ʘౣ^w\T\?l2'?“d+<$`/[Į1jAEєרܼQye{ ZQ}ZÒ,_ sb]Q4c޻Z5+Hd^q&֗r_iPUK ʜ[dH=U:un-yN/ U΅^W&FLZ z}}O<ѮOϦ;уǿ^wOx8q9pO:o 08:3{2e ,n9Z*C:A 9I6WxXw:ouoGvg\Ź1i?r֬$]-Yp~]㢚7&4 @v[%3,"$7~}Dݧ FL@r/]{չW8p"GDL9RCf)”]j6[UCNHb^6(K;%3>Xٔ5| ZiV-; U Ϯc{%{cܱUvH%sioFOS1_NGόv-\ 0FtMbqaQGi^wW4UdI 0~Q(1_-V|1Fvi?[*իK_M 5T㊬ {w^uGSO\Ij+r z <2.^IUaL%3'?`lEOk,$çNby|K_ߗ/ՋO~[޿^SL(nsIs yBSr(UQ&F0fA}KhةSƐ?d+bӦk}d.zvƪ9%6dYufZ,2ÜUzO h7/ F}-e?9R㧽w8rM{h,7XELN8 >ۢ+ % mڥv+:7'y9x0:>sfo;4O*s1IS'SO>g~/?3_Nհ]퐄 }FP v\ (>_߁s'G0G3ŅmH)9*X}pnlZ4kTq@0~23i=s$sW]WVx1Bt'~Y?/[!(Hʫ=xOFpG^u_P³hT~z?ZcK?jNp(.y΂/BG7ꮏP1ko1Ɠ_ފ$u*>U&ފlcto+^ukucdTCBN;FⓃ z% {[/T{*ۋL\-&sp0@!/O,W9΢"̮3Qs snL0́c`pZcs?ɮk3hF?=_s @Wc`g__oFnT0pޜj5Ohбԡ+r 8!!Aa܉0 g8oQ;S:~:թ'yɴKa"P  S3Eo>vפICfp>ʹ>1=Fm٫Uֵ&*Y,1Ee @.sSNKL)swfX9OŦc 'θΑzs; 0(פ8vgtZGZ&Q yBfHh5Ob/}_1R?d@߿}㳉ODSpܼgʀ{;DYlRNQNdqʗ=͕_,ˊ؜ibJ%?rwn2p*Ru|n۹iׅ0)c}'Ky!0ؿmoضj3_`oSzԨǕb [~#o_#?(\pLb|=Ԝ^jFSd]P&2+\!viU x`ԋ##J,Vԍ l_޾ЙÙ * 8p*- m%qCƽ8`Ҕ_|ΟYO?x'~tmvwķ4!AT`0S\.Ҥ5!1HGC HCȀV;5y_[֍ ?O8( d_q W@0VUTH[9WKg)M%ș_TAa !w :PZmy7*RjD/FԘ} F(Bɡ)ҾSSMXMtZ3OV\/'tҬA0T(e馓33f\5+MjR?Csl~3#Kذ樂PCbb`7{ a]\JV\t(∌1YV:k-o/~k֬@\6+C`.7髟=t1E~LHcAüO1IvIRas%eJ@%=Sw&[#sX7o;_RmhQ\L!0V 0=wDŽ9`1Ϊ}*A@GȗuPN͘U'.X@NK*Y]{92(jK\@r^΁ʈ0Dpl]礝Զɀ{[kժry\hA^Nb%IFFLp?V "BvoJff^ A.#qKr];%T$rJNt\rIeYU*Fm4~&Jbś?w9cazDZVA7#>yQ!IZ dίFyy=iI"[IY<֮s:9711~Og i BubwqEA  w/@ 6'թ3jY+.|&޷z=U.z mAviTrF獠kB\%S"ߘR_Ιo۪^̈́O`Ya+up: >|ڵ#oJ/B0j܌l}-19U蹧d>_zLۏ ztR?S'o*'U\uE_ۧ]M?|jq!DUhA;׭;琥BK",gs7y_q D7r8.E)-gYBf7":#t3CI(  dW4j'<؛qslMT ]#A@ S'~s%ףD_ܟ֣~n%,}1`!PRT`ˮp:Y0 {zuaw{r,=i>闩/}^D\Gg6&K}?)gM_k.u|GJ"UB #"@+08BQk_68Rg(qV;׽O>[rmؼkiult<)EBgW?8+%dy#9ѓg/Z]a>oوAۅy@#D C), #w{c\nYj)F, u >ןPrTxịڶ80z5yIۿ9hs{~`^fRMQ\rM%9נr7x7j8Ƹ@WG4!{Ӂܭ9ɻwg 8q11Ƙ/mkMMq$ołOg-ʜ.Uv$qw `jpS&KR*leiٌ4!2D# ܻL7ygIPL2T3 9Iб7ig0| ~Q9!6k_:T "( g @*]xL*~ߝNUU ƖU۾E>:w|8Wa `PL D8%3,K"o[^Fm֊10BΠM kӹ}wz{x;mbhJs6Z?\ܭ:qX^I^!3@ Pg(LEE"@c!>֒Ut9*>hWؾc^&Tr[f>ܾw-S0p`N{Xo}KmZekŅ{ctSa/qa̰>z\%Xb*=U:/>1IrMs%!FoEAQ_d+[' ;xsH#vZxڝ9`7 ^ڷjRg`w,ˑ!9Z9sq9J Tޥg6}o=xA=[%?Ey%E%rU^!%C9KrɲKR\s$KrI*8eJ^!?Sc a+`b q6?=61vڈgc6f}q@O'Tܝȋ d8n ""G֩gRItlpԣ39RN\,B籓g'H;Sسo+B ˆ.9goç ')&P*S!qTMD)@Uy>eBl؞3 >֨H'M_'1~a>J;T 8왩o߾}7IU?efov(_P3H%t2Ƃ@|F{:JK扤΃ (Еsӣ-T֬3`Do2ЬQ-YtJ7>qtDF*-[IJܼ`z?fXog 5H|`q}$٬kNzG% 8ͮ8(=QU35g U[AeW’JTG` (!ڲN0Vx.)*囔JER^b$,Isޤu{++.߶fwaq3^uy1 qU ĥ&N aW%rgM!M]Fr: SOMٿ/A`x⾾FZ ,%fsYYKTjASꢢj׊dSD-zL^ bME NnpK sR_b;*Ơ   4iZP`sINӜkٹ)]:N*NMD@s>Yh(WIgrs zmi4! :3N:x()?\4~~ D_g0|Fd̹CbNK/mx4m_9-*"?&IZ:JRrZVnUkGl^QDh-.(}iǑcW/xwBr; z%-m[]dA۲!jT~8w⧿xc|]-WNt@ǽ9|YN%s}?,'}1N; %Y9/_~pX/4߰)&q['>w2F!HxӦ?Z|L^`lT{o?0P9憥V9 PNF#W' w`Z0N.ΐ+bApYy; 0էcΡᡄ@?TbDw+)Z-mԮyw  7vub@es`UOۛ7㕙iIgh@b)/-:RJQ87J7ێɡFPsʭV<;(bE4Z?fjՂ8g9)w2p`2%JebsQaRfPEE0P)@#R٤?WTjޘ>hY!~_L>߳oKJ'gΑ*=~fG)-PtaMF?n 0uT\h^CUX= ^q.z(DS"V\&_$m^2~boJg-~:Ŀ77}!# קK5+ ~Cova>Ըo1;\FyQ/ԯ T;k[Ǻ֍ M[ZZ=> mZܺ*ɾF⛓poݲ߾Z&͚5j.a}CpZTB1YY9!0\Lt~W,B` +Oί,~91%`M/,rΙ?>VשS]׮h 4 R<~u[9o&`W+K.Yr9 ItO.kϊ,QI$+VU/1}zn|nGb.߾ /djT eyE99%Eť K*$0Hl 4h* yeWa.ە2[iqyIAiNVq~viEy"K@*$evV=m.|]BNBBm]Ѡ:+4$`1;O)s?誖Aƭ?kj>-ZA{:!pߣWכٲmtC’ۆ=;՛ͩ,-|*d,:2xςo%=SQh{i?|c@mB` (h.vnN]m{:eb2F"W[Ҳ "wcB-pQuKaO&Ey5߂!X]$YY<Ψs'8~( Ui J. 9岔[ʋ̩g$<~3BiSI]A˹̸&^響{y0^WZfQBBLj#qqYA%lw~:SQZN l,Q؈ A1y钟6~ Ӕ/MⲴsy?Z[n(2Z*̥E܂"siju)h"b"bCcbbbBu:NQay:q J)')Wv\YZh)+.4y9%LA[Euۇs?4auEU`@ߝsπ 9ex,٩иȸѤk .goV~ݛKJ-.b4CC#"BB z0*[Eaw}^(?}wﶾB:Ͼz!4sS?5=#* L۸~e ~] #ds8{ykSW5ե_'kϿo|i#opJ\WGЛZ|n_]u Ã߹;B<:^sAK_4ƸmdzyȘa?~@;4w}n߈spRQ-̲ؖⷿ>_ȐZ*=M:NA` Ш30F\.JE/'`>c`qQ0YTpQB!JҎݴS%%hAj!+,v \\R\\d֏k֥Y BbuSAsJ~ƗZ9x3)9>!"&>PqPdd`;8G3}..1`$0 YDOZr?`|k{~LDߠW\auSr3 M4~wuձ)e .n6Zj.+*.*))*./,p0QD&}h>"20"",.6(#6oƺɴn^T\vUkm}A; _BD O8_ycc33 !鹍?G򃧟}|/]`yq^ Fg͌ʝ1*(*k}ܫO;61Ύ}'jiGёwtS-?7;_MF*.<0P0so6~ƨ]o`!)FU XXavFʱ&%gr-gҊ* =]+PTa*):g[IiQ1!A!~$ Ce"4Cf1ͬqx4w5hm܄[uBryAiAv^Ʃܼ"siyEEKL2gaޫJT DƇ%DGEF։ Ո`9WE 넫?O|ۓˡ,q'BV AzȲ8K8yO7>qҩm::"?4d0jQ͘ۊ>}牰Ê5;O+,,V؁1 !FcߠDUVTnYy&[l.ᐝtp+2g s(vV\\VTZ^XT_TT^ap8 QEBYuy+w8ٟ0iB6S;%q7q*<`, " kEh0j EԬ](@a15Sr2˒';k.-Hútj^g_;&Z: }U<6aX '_킵Wy]o14Lfzao^:%*=l*Хv ~|U-EQMjܯA~/=t4_[jx;3oxr?zuب=kfԌ\JHjF^yCFЀ΋{NJs ; 75σW*Q.**zk{XO {֍EQc`eQL(P`؄qF +ʬ"М_ZXTQRj-3W$( :F͢Aɣ#]*Xe\)\ 9Ɯ.)).'Rp$S٭efKI%$%"A::8o7 yp/S!uaM`e{lyAV1Ej.)6wkգc@88GTKLah>H҉VI'[7߻yÖѳg3&{{0m'GȷnLɡc]{ry֭} 'Q[(LSxcg17`I5&MpqApɗ]C045-}f{i^iԂjT?_3jӪW/xK7Rݸaxc骖*Q8ueYYK_z[owѦy[tbyy6\ثkvئA@3_>WeaZ@ZaAɲϤ=3vO׶ PU<"n+,0gqIv!$'9sٷGo]sV<-sD1j3"ݷ8+j]7n^@cV L߭רNAdqJ |8nʨF{i}%fmޟX?626(U7D։k\a\|lٙD7mRku. K:s25ٴ3i9" pS݄ȈJVdpجNGqG5=8F*a#@)HaÖ?CQL&vhߩe f/X KJJ3sr v 'ؘȰ؄¢C;W {f1CB"B4*"ˈsF/(;|ӧlv[\D]Z6k@] Wk)Hm0F詚 LN3Gj}bz9{&鋺v[ `?rU-C53<4X;wߩYEkVjRj7Gob{q41v7wyC'M>|E`q^22|Uw^#oQ c'51ŋ.c4Ol0^QA08&p@Ur@Pݥn"H8l?u<~48!js:li%)ԞiOϒ Ck-hӭ5QKad8u*T \[3b !Gz({7x{G[WbPK~?l.NvũE7[d1ФC#,TsA^^qpܴTsYb)\gVV&uءr6HyG^yQcGkф Z@ HSբ}G>77 Ǚ}#O?۰ivU{ߝ}v֋/};~[sǎ9 (DTE)!#!t9pLj:uDcyf)*9#MݛBbAZO:ΕIv:[T=z>1]˦~zFe} bTkBk7nزquFA*5BfaYেzq{zLZ& &?V'"KbƉU7x($*>LL!#B8 ! _1p׮\N4bMk{@3P%{WgfwOswqWPzB wsݙKrx| !yfowv>} Yn}i+?NF޺pVu7mϦeRH;S Y,DB~Cf8#Hԛ\3&B+FWiD $"F0!rZ3©Ll\lE>;8!){#LZsM{|^c6gӞN/XDD4MSU7u:`xo&  9u+)\j6m1ٔZ\嫬Y\V)=c2PG+swm\cTy@+8P !b=qH`PRO[ܥW]>x?9G;}V{P e&v~7f$I\41}(ăg9Jr,g2MfrTc4fse͡jpcEe%EMZ{ZW6pĩuWzv/+*ưcWpdt֭/g-xk8㗡[IK#'9#kֳfMX+qY;7o<\FFF8pHWނspvjTsE߮0|Лk5n}}~(1=Ko0 kq5EEU&vwH"Qlߙ3IΨU}>Գq ZM&qnro -P*K<@&mU (Ӳ-@" "`yYO/EVq;r7fݻ)9ʪCUvB!3vxW~<5,+6/R•{:ۼQu1MVg]. 729kBSt Vg49Q](`\b0"Ps\j5YOcCv[/#v.#ԅ~ò&ng[;j iFUh@`}C$@"4\.%W$/Z…LEW/0::(2-WJ# }=;6vD`) !)c ܼcw?Z#ΏG60" deem%3!RezN`[79q_غdGzR i.{,^c켋D*Ca? O{vf~-4 +o4R֟޶`}^=l,GNn&E\V3*ԭ$O? ]ۄP3=#cB`MF[29TLQL >@Ȅvl)-)Ue$gf: Ce.>a>>^27wP@,06U(DBl6.λ|Li+Rj\=\1m6uDUt>U]ӓ2FsV:&]e6.r`vK uc%ԋ9k:l|`ZoubVmҡu7\ MSepjF<Z$8~ZzInQhTok&X;~|!ްGӜg.጑y5HظntN׋Dv2wVW $AH ! 'C p@Ti~v8{;Lgx(|Be..!t:^SVkiv1~O% h1&²RnCeϧ tW%+{-4*8}d_ Dž k 5e*+ȇ9`9-|よ`rJeݹ\3%`,O,\_㼂NEڴ7Knݺݢ}}g}=n\]#*w% ۘ?̖ʇ3F^J!ԩ{'X@1EJ ;¤cneܬܛWo$\K-)V6* r\^?<7OY=M8;o56̜ө>ҘH$>v:qZ =d}7rbk٦Ag8`s K JK$ub[6,!-{I]E)(H ^I,J pwJK%*!Aüac%654E:@!iṱfm8˿0~ݛuk߰qhXHx5)NQ:9%s A?W}hwi؇1g9pv[ Nn˹yVt;Έ#+ 6Ѯ3FLe*(2de2MRPaL $b@<9e aa1|Ց.E qBC4&EL%yCaU#x Z{kɾ>VkszTqn}7nb9֝ckwkYYX=$X판~?Nнm[ Eb6ԩ4m9r뮾D~9q%e%z#sP.u)/# Ϛ<{-g.g&/T&aDfZYfزdњٿf6릐ĔgnoyaY1?|pNHB j{CC#BA?SB@(*0jfUe:E* xOM*ؾY83PrLi}WU X\euTa'y9fs쏳>y|YW֭U#!NivĜ,oO:`;HI" pֲoVPUt<RAL8L\.ߤo`EA8p&jTIi)99Y nlZ Fc Rb*72<88$WpygB($gTC2O @ Jݛ0.MZ鲳y6 Q E-6*>!gݫ.-)Цـ>(gt~"xJ (ESZuFe-{Xw\[6Ɲ JsqC%g/_f>*GD(*oeNU(zAHQN>y!BQdo eƜuS)ݭ]w7KĬ ?.<]@Tժ?!Og( pAG@ 8"m%5mU38'\MHP&&27t 'S/۸sŢ{ JT&n5+VyW3nlh 6ѳ{JBҭCT3~mw?[l5;VmH~cG^k`B )Q0R7OYXoу{uګkL* iݲyPiYsNn?yڶo^l q`d;k{ iJHJxDI[rx|[u|-ʼF )Vm:6o&j7VkJD9Oi^K$t*Ty%]}"tܨ΄nwQϪ@J튍vwuyohg=? #,_l! y7A1!z@c7j9w&rnZV5'-].ٷc[LatQ"eƕ)?}ԧsrKh*Q $t}F .G܅p^~ 0_b 6^~5m_0;!y ܜ;9Vv\ڱ-{u%E&&?d1b [dMV^JKҮgt: &l6b2f+o62ӹ#Lzb&3og͎m6laѦh"Vh &Vk 6NӞvƪ7yebԜr36Y+^eעGW/֦'4ܢq<*8?'k+={rk{}#`_~5V<͊f]dNn궭z}t߆]}5ʕ[9o?Ξ۔)_KܥoXt!]嗙.it6h٢B*NNLpwiղALHF?#<Qi]8r:)zDL$ E"RõFۀmƹZYff ئU~G =& enYť*M^Qɍ4n?}w;$"0ט>1|"%B4O ./QYxIy`DKd}pB4Tfnγn:}ftk$ߗX*]]]y?MQgP0㢃kD;ɓ]tmW\ jWsFxå%*%d6>U=[D3bv W* '^rf؄1C?7wh,,O$L`[LSj=CTB"( c[2]7ԭݴc Z+,|yPMXnn{ܻCC3U0ce]n^n-pR~ٺCVʾ U ÑݝNJۺ<|z!V_32< : F+jlB/Xxũ2`N<Ʊ$ߪ .@Q}/B8;9ԯCn/unS/S"~U8Y?ZocYYU@BB>r4vdW_g+[l5̙Inˊ-{v埚psɗ}d۾.b ׮e'fu@J%QZǛm4EeWͺWաRe++T*TJ_mky3O(R%_h3Z ym=MQƔSd+sy䫹Vm?yKUt#;f,;r2 azu;EGx*dBP/&2z]sd?Vϝ|S[^jE%i|9cϡJ%+-}-Z}Mi6MMVսW_~i]{6_=gY_-s{wC>#)-=wiЬX477gޜ+_`5UR/($'9T`B۴0-=*K@6 @(BT"(B D֕iU=0JO{ =e~CC<}\b@Ⱥ՛vl QQ:{fWͳ%!-;qweid!HQmg%l} omD)ǿݽ,75[z4nzcͽzF$"8X DH8?uzyuݲt`"h݂,ZLں>]׋s˄J{B B\q?*_bߠVd՝T=h5[l^Uy1zPGg^y:*,r W N# sF8#X<_5++&Д2훥2)A//cM37)RǏ8hRH.`R1R8U&mݬևZ0k:ywN1#W3y}tFk@U~J;VASUA E (} 5zd XNE Y+p-]YhW7iP/_swx*:.MDS+*Y|e2IZa]z6̿L@d1MܻpQ[XРsz \9'/w#bF?~и؀jrwwZ$bL݋w'nt¬삌{79Yp54!ӪRJ2Ey)Db+sݤ9aɕEr0P^޵լ g 42""Ǩ͖mv[ N;*4&C$C ,fKIagvmݙ"MhޮqWoj͛yzӦ]apkTJUh?o(Xo%D}4|C+6o/3IܜXGE-ޝ0k߮PHxҩK ofU{DR8kGn~rS:Dô;EKVqݨS;=4: b}|$gY;Lln xUPث,!c~?p,¿|5ljC^z0|m:{:g{!2H{fNQJF;&^d=[7Q$`? U !PL!! V@@;kW=yAcuufh QqsCFt DTU\ \6hv 7؏=w#{b۴νq%) #  Q=0@*;s:7no$_͔P#RC4꼔T(O?_Ũ/( Y!ĘpD!4ڴ[%BwqQB,:r&^i7xud /B {'m#\\9Y%iI)" -5mph8f^^j^qdd_Ңk p`;hΒ  lJVn͝;b'O"<*tg.\vouܱuR-Zy֨jKt*¥KuCJ2zܨ}I:1k;x[ёr+`Fj;ds~$CEZ{[ם}#dЍjm>.oJ#Š ֗I%>~bl(ηh4BYU>u!;:1&"Y:" -`D\Tp୧ʴ%gQ|LtN^I_:#ߥmHϽj/_2lrǿ\6,rm9'.ZoBk1''D'OҦyd_-Xcn/,u+xf /ws1<9\mPo0wC jӊfUqh xْ_:v37oj}=|gxU9`0]Q%!t`QUx@ œ }аYժ6ڐ}pHLPPW+)h P%:esH&]ǍcƆijgS⯨ x;/`hpVգ_xe_UXVP/Sf84 ,R$m} ݤ< %ײ+*AFX@UQPk@2,.g}<`H,LA^~vN^ݤK i)ta>^yZbb˦@$0s'}~QUJLu굂lZUTv-/^մI|a0f\)S۾iFg^^lP.ܱ}'3[,6ЄIvlE hrl"!1X3@(Z5=zsmڣFoШfk4YoJ-+6"@p}}<fcJ@Bڢ'9wU j5<<KiI$,EaBghN^urBԢQ`? #@"1!CC } 1Fm9Q)?c'OGyT~dފ=3 y}{|A:DUsfAuVnc۵w'1S4۬z5cC욮IPaRsCxGYmUӣ2`-LiҴ+ٰN~C,jykO]}d .F,>a!uI$Rkھ;>}&;a|O>?˾M^~y2r^>bO0% M(y~7Vlo)ͩF# `K `:5?}D sJ=<}c߷_޺o-yzk=~gv:]Ұ]uWM&6;*oJpo6,6ҳsq^^rxz1 &T@or?! B'C'^9q[;)fSŲ x;m>h*;p {^c"Q)JѩLv!v4^ҧd7TZfAsq~H$x%>Ԍ"^ltDU@qvV `lwa'GvU/|Bg6r2Tڽ{ѤQ / ۴IGEwnך}'!xrNY󬕿xgD뷜x,YʂѴT ( 1@H$q padRDQ@ @D<1 j%# f#/5~q7 U3#Vu iK-KKKvQ<3XD֯ٽC'3֛MHP/Ի3#6'y;ؗ Ԩ7s=#t < jG:3NJ4yj6V|g3§.$ AY{{ɢUw{B~ EYEiy׭QM"%UU_Æ1\&}:115ع[$R~0&BG4Dqͤ31+,p \SA* ״}]o{wv;@W6MtsYnS';s-}>?Oy/ǓrM)\8 IHJDTK @Q#'vqR#O֩nD~h &ڥ"W0kױ; g UyڵSӡ:{߷㇎ֳmn[s۶HZ5&~7zb,Hx,J$"`,Sk 2 "B*35)8g/n'?|`mݦUdLxnRα:AԚtk#b0O.rhN_9[-ԕ`;(2)sŞza:5pltq}DAj vr#D x;$kʜ"bDX(ʨ# Nc]ժdQ*@)B<)-p3A~C{x}`PF DAD"!Dcw`X@G c.yՂ O+R*'iEE፻ܾA=N @BfIi4U#<|TOWg*_)kIiYAXZ=r3!A<;yCޓ.^K,*Q;#[VD.Uجyد͚]Na`BȆ'1W^~BN>yHE!Xk BY\o.Lë[mP?9x|;&@wT K~(C |LKT=;UUGjգߍ^UP] S`@!@B*'‚@YX!DB7{ BnkME٦4)EEP3Vdt5o?e1m/G9&3'oܳhoxXSTJ<< >~//ÝrWPrZ"4EA<D"XdZ9=Фi J=C NF_~*?|O&_PBH I9/$!D 0eXUejJ^Cci}ϫeo /-]yS)ymizN-7P+vphK~ac~pISհGq4;&>/?f5ۭB `ݺv'M;~Z5*qrWS(34`E]?y#P)9>&:d=rvڤikm_Ej@o'I+Ynu?0/_u"!Ny[1nP+™8~V\i/q[3;٧FmD.7n]p88+6"|5__[NVgdkx}Bg'RU!1} ^U24@!F!1 } \:eT !@Zo9.O=8j*A#`\dBad*=#)W3PRxCbCB"B.ZEv+%N:# @c' yjVTyE .m~͛G]Ӳsðbj.^~31Y(C?׬hPyxb7"0<;اIPU*63UGwS#QBD$ibodZF%I)+5j y_ێhFt~k]!Qv\+wMFCDWZ4-Ts̚w¸Mb( BlO($*W8xӀu[0JiQw rEL(1 jch iM=~6Dj -R)-vg./oePhJUC ˃5O?g9$ 'Q4$ @sS 4{y*U(z> ٛFN6:MjR_%nǓvxB"1a(}`s68Xɡڮ7%Fby[u3Sm,`qhz.p}۬]D~z^{Z+?z=J,lB3N5#Ǿ@0E좘cI>BoO>VMZ$ ^E8ObWU/P~ GV a}p!(0͢)3 etY>-E 2tgٶd㹽Ǖ%sbWi˞-]+.{BBX0hRm=woE\E`6jmo\;pdD~-cc#} y:_|2&dGwZպǐ#>sd:57" GC V;踼DiY_8s݄Rlk˴7{ʪVm^v҂fk;cs*=P)݇nٴG̀:BW@4a # @nUe*}qEoPnsQ}u I-'9bC͆5jiKRJG0@ ]v?lz^D`=uP=$rۭ9Pe]z=‚pņ#N__M^EX$T8/b)@H.M,,V s )SߑKnZ Nʙt!91}@&-CA:K[u$<&ג^{nB[_h."qXo{x=Zz2!,g/e4YЪ_9R@f9;z!o\鈓 /8 ^'VL=~pXXIhڪp~f?t'@HJh @S=!b/9Y4ejAtuZ}[w `{X=w+3!.7**θriߵU5;铮C{/^DtPlpNM괮/sUC3Iw1XU,\9jӵnUg'A?<_ B NMI߹śV-ߺmӡKgn+)EKЉ;.YybkbozRV@䓮]4k,#,]DFͫ}BRVJJW|2;:-Z֐y޼:I"oت (NI+B4D~yKsQ֭ѵA ՍKDޏ[%OO0 ~Vߏv\8N^̲"@8A5cC7c'Wa BG Bm;or{ѩtQ:댰Fgp;kg:BH5_")4NsJ]|Z)}D#9\.rd(a˶|=hd"MC}}ZTZ_fYe]:^JOZFTA~ٖ=NEoxݚ/U0BfXv .{ʶ}tBsu2K 0ǵhרnHIͥ_| D!Z?y&'~>n}5Tqy[J_0v|:/NEB.*V%$gêXgrB ッxU9 EQ_J=u;hG~?zf|0q~͊[.U zf BE#Q4,^ !lFuy3ڀ uPec3YYP(붫66~̯w|ca29#RJ˶c 4JZٻ&kԂ"Mj"Wѭjb(䑯G@Tl>*,Q3+~ 0B i "BҔ$PQ݅NԌO"v_;c@tFkM:%fn.]2gKZт^rŘ(d#wRL͔5FCB3팿d:7 +䜩nUMf蜜I: !* _˝Bգg/%<2?Ęo4UU"R*hE}'1w%hJm^AAAi33r":{hѩXضh?_8o~@ 8B)y MD֪UM,7ny_zVa?/~ - $Y(HC!Ey 9/I/p0sPX]XߒnK 縗_ jq"ڸ~=a I|2}Yf|͂_>cβycm]%-ZLS@qP^f͆fo^ԍ[78b塒Cwt\ b ]b7 pb^Xo1dr 2A -"/o3xryy6ܽq̱R1]n DrQ14!0rfU4*r)(J-і6q`}ˠO eƽFBP4CH"ruc/!"p4ݮWk LѡQBaYjVYn!*wj4#'o[~`סK.!zX*Hp  agky{ݷ8X* c" }?:62:j_->D!DQhsvW ,=w1_.h?73~;IYO݀4cWAG*渎jRI-{\ B<)Lۉ~W7Jcm{PʛJ ۷ӿ4b|oH`W]_VܼZiI mu>C*/ a$VFvў×Qi !x7){ӎSntSKʴPP9s )E7T,TUHRd2eCn>{hO: n KR/WW6G>z߿3ELݾn^xߩ赁s={m>n3?ۯ嫉W6!XLf1@пȿk@/se<ܺt{Ѵcy"%(дF EyYɪ-+J -ξ]w,`M` f@/$P@ MߪB79iGH( df-FDCD![m9vRkmp`njՐK]OO퉤RL!Q4<%NGA/z=Q/y%G,L7~P5үs6&-`Lfۗь)9OPBHn~W<\«0˵oYCLE۬vɲr1g.. j0&qo;_kmwWjBS :r3`Bu\r}5oR? o<}L4CşN=njJ*_*a+(V07+J@/_;ծEݛYyQij* f&٦C>ZyN|7іЇN9US!p~K\4g&OG"r,C x{lS|=-c-ɿnȠeWuܹmLj:ƕŒj yݵIG\U A9Zmn>R@›BlVRRjYpJog٬܂Wlurђۼh8y;)9Tg2ѐA`D6l3IWU$'dɿVT mv yP>J  *B@ J 7.d޸]A,ŚS3J@i:5Sf&։۽] WL!r]\MaOH'0jMzWC+1mټּƈHx7hൣBB;i>k=wfT LX&;3oN;}17R%תW+^gm Iy:%g/ܦ+P hCs_>y._O)+ѼМ[LN2_Klf2\|/?Wٲn|wܢ"%]ș lyW8o?L,I^~JNekA~SFBzϑ|L~[NrIK>yE%u( )KԇO]wF8$GR D#<}Gj0erQ?R-B,+-dFXBQԵ[iTF`"ii꣇RZ^FءW 4]S4E(d7j-g1 hGZ_$~`!%7n?󝬴h#oݬ+Hi&MӚL\[0QX?MfO[V&?0d.$:L Gl9;V;~Ҫg#ka8@ϡ=Fc?<,!MV2[S @(hѷo1ǖssKbjr a9C9L M$"><|~ a݂<_@"ylA-#˖_dWZ9offU@H)(c-,޾~ .\}֟s%;7n>y<`ey CQ iiH1DZfAWR'Mͻ}3)7!$Lo5ŀ6Z(wSu%YV@EQ̞rݧӹMncG)e ]\0CcX !ykow$THBx˜>}:)jޝ8 sӳ i!S) BfBڠ1>vQI]4Z37`% !bQݜ,~+H6ٵ]p,Ň}WɬGVL6Z)qφ|8>qY_ pĬ狁! +>1Y wNe hcAh 5=ӎ|GL2a.t􅄡*8Bp#cZ`0'_IP6Mk:)ӛ>vqnN2#Zjͻ-6{\e`קi*'pά)XتI\_!$mr͌]z^r",B􊉈(XTJ oLX` 6+ "EU .j>d#V+6زʗW!y"ތ&̲ƘRjRFlVDB!@ B"H M2:?0-;쫉TY;iiF@@(D(P-\HݾY7[M"=h77D3|XO=PEG4?\b>IØ8Qݟ wj9vMRe"iD:f疀ʡޡqݸpgвSÂ$Jb=ܘ݆i|B1/X <BZ$8uV!?N_L9~FzvT9}ڴs{ԍS^t4q!!3|G$_V*AH kIO'n۪g{ﳗv7(Q m2[jW Oiߏ _!D `F hdrݔџ))ռ| L!,;z zZkxeBf;#аv:_T? B%; ڷԪՈhۮeY^+L!Xi FﻗT"2+%)i&;KX%/wAJZi3hm#iHy(h#vTѪu&d+ o!`XVk/)d3lYZ ޻a#;VT9DWtP: "@3jX/1Cƽ7pMԑI3|d۲`]WXl SH(iD!6٢5JuQL͙@Hѐ)ZձޛlS"Cصg^^" \H@t8PM'=0!ᅏ߬$!o#p\&StW((VMyy?@ JRw5i󷩵'|%!1vr}wh5q1!5<ԵH0!2-X:f4p/,9umڽ,2cūwLMnA Iay$6Cv^`5VhK3ӝ1$b/He8Z+ăiK>y,bY?;="'38s%ߎEBָvvo;%]*A`X9ڇnūU{WR<˅uT"zHVm>}O](!PPX͢O&-Vi 4#D*Y> c# B !?}>cOwӮ]~ui/aנ(D +~o3 '^+6t/KL ~poYǙ]Wo0?}mwLE=!btW?Whע ^5[J{yKwzkC@mO|zr?^d㛦) Ww[_m5`y{F'X˿7r굛?X&cГ`cWf'}6K1BQ0'[7;[w9ּqĤFCG59;(*X\Բ{)gI]x rs +E\׏a fh ЎB B( 2Y)LgV [ O @Hg0 Rn\M}%/'ESV= ޡYYZ/HEbM&"@QTi-Flpwqr u{x($"QB0X;g.7٢A;!"H( &Gⷣ?>"G:+&"+6킉C$5yy'ϢgLǍ֮ym\ 0~iAJBJU[c3A.nM+O#-`ѷ:A~W&xf͔5OW, }!/h!@`5;7mEwoOq# {mDx ?BH!hd1Zpg0fL\3@[pTI{{9,OZȜ0hRSt$#տC:io4Ym%ke=-~^Z MrftDy$'(-,wz"4`g)!ig/]趽ا2ɶ.}z^6(\O4phyҾ~n#tչqB8쏎O.!AL}"΋K ܩmZrWysw?=,&%<˿*\"[#Fmvy_Use?YoehXvRХn踸h???T"Ȇ-c:l6!Cլ1h@׺|4 w``a~ʬ]wQjf3>.!Hu6q:5,g_r줪H"  <ܽhyB1 cq6jjUzRRg5Vc9@gol;tpbݎ}g:kaβ&I742F3 F!%uzxRDyd4+eBFch V DFY,azth"(.H*eh !? |8FT6:=*/ըnԀ-׏rɤ1?#,P`QsՄl&߿& b+-L۩k ^S;Ո *+&ʾM<;6ٍ2~gOxe*}PHwNjU+6L.`{]|[0j/T?ws㓬Ok/p̝ﭖjٛˤz5# qi@ewOӡKwStkګsӰ` BX,WX:z G.'>鞼xcFu{ՑYU ? 6=WnO#4C؆oOǁeL,۽+v;skZ?qB}彇w y₎'> "bOMOs47Ǡ d2F(bhT&@@SqFn :QgYl,gu~2| 'N]RY@M}|8"#dyn5zfՙV V@E(WrͅfflЂ>=b.TB膏]>>o I108jn<\l3}i?8"\~J o~2iexoYYQ!5==<&IȓC|βY%Rro'fpdy{Λ2v`֯4_uu??_ 4T:;v/ lިzu׍]=˵b w|wL3 srF;|a]L]Fd2Kד/ p?T4Գc14qPaۉY{ڿȠ6k[z/w =Tsz\,_~6r/3p(g|VozBmغnQq1!/֋fdݸqZc# AJBQH75SEtO&?(8c&t}Ы6d,' [5kӬfjq!~#?&G@r$e_մ§]n\P@ *\C E͞uC|;kظ.E=]޽`mdvڍ{3 /&9tH RH%ՙ]:4!pual)RT*!$"DV:paaT= b4kbsH}QsV%E|ARZFTUjefp<0&s1cQUO=t~Zqouih63ZyM.NdKD2aAt&cZ@$wWy0H?CHȽt9W2BЍ\k1sf+10D re",߫f!~ޞ T$ "@HJvfVђ_L,s/%LS Zǧ-URkiny:'@{_Ds2i`0-{> 婈U=,&"0(hgYf2[5:sm֣C9ߏz6@tN~I_=[;WPRRl%7{SʤDдcg9lL;2rY Ep"GOv{_ ~ g>s{L.z.H(p>;˙-Vܱbq 0bǮlvR&E"hr_fdZ^ChGoբr&/t =M_\IOZ}Zk7HDR!T6f7iѷ_Μ8tMky( $<*;h1xͿz/$*[&^'6~蔣r>6Q>>>(WvdSW~s쫴bc_ gwO{O@uW=E{7\i*W~g嬲e~!WSWT'<+T^ ;]泏r,AwZ9gϽеWxdu܇ y'O^kYvTMMl6_~e7;u!{ڣ)#c`ZSԲ qG CtczA,r*N$x:"e h[mH1 眳zEmoqױ&#r ]'jG0?gL$DS<E8 pЁCvXC8!D<4q1=0qc>_d`Z2M MΛ (aOW M}/.|ގ_^oǶ'#sID}Isv]o~3-7ya~8f|KB{W³ԱEog}DcLgtmч/uZNÜu1{ؒ}O??沣XϽo^^]S_{ᶑD$ZY~ȃ>?`O/ATE!;6/ '@T+4ФFLy]6S  bm {qEQF3_^yͷ޻v\jIdCF_H(kdT.m5 /6Imw$D Θ+H(zK%hD U]* S! %C%h{K[b*{8gSS]@JxnFU?˟.?<婭mV@2˲3CgẺ/YԱxQXD w5H~ 1Fg {½nXv"W^v‘.Ggr蟮?^k11kfO;4_-$![;~C78wҍ?̲%^Jra9o<xM c;X'?tĔ }:X4U{j_zpZֲ%o_Hp=?G:`FDB7}缂O[,U :B;~O9ztA n_wO䃼$'b~?;Orw{~c"xNݏ>={ƫ5]U%8=`lw)X6٧<b@'m[FX%!HSkR:dJ9h5U&4E*9Xڿp'7yߓ d%l=j˓om2%3_gV6}t~/}w?^!90Tqd\ |Md,5/Ze6%$W,G/7۽CEm,&wpq 3`B:矰o|CLF?/X*S8spCtƆGRXӏ)LTTZm )IJ" @JGJG("q]rw@8@O~0A~q2c5?o$؇RS:ѫP*/ǫ ,_:C4mo>>ݕx9N0{ ׅKp^\S{.<NZ5$쫟yeJ36\KWv i-Y0UE ֝N|K~_<¶RRJzu>ogN .yw^sN?B8b_un['S}U3]UeOH!گ\JUft^O}3~=bm[vBlَ#eaJZqQԩT-RAXLԨ.P|ǝ} nYu(gj}n}-9{/ vd! u$gK9kђ1Zp}纯}_6me 'g]ۿ?c~&8rvJmRin-5Kw/οD/Ž d$h:%ɎX8* # n3O>6ߢ{ɉy~v8-5&R*]q{BWPxނD}yt K ю!b]}O|[x^Ox+y83;elE+%11!A"@&I87>{tY/}531zB ]뮻~3SUR7hY̹7gW?E(5M)P:}hDZlL>/.;K/EÚҞr{6$=AHS{_Zv"ːM%{!% +P'h**Xomwg>;~-żY(maţD"ӝ) 4b$!g M{?#sU6t'tx$BD\:}M tvQ.4Ba|.h; x`;ʫW[;z#c/9ŀ*?%9CU?}v ==o>a b^*>W\/bO?zשbLWsun[a?k}]guʡuL{DB7^3i/754g Z4t\Uz}芟߲uUUcmqA\Q]6Wi/y_]x Խo45w}v?7E(3r֑|? W\~'qñ:Ѭ5+SAUej lR2IP۸jw/暇u% C575>joiS ?Sv{޺z-;꨾9ǷoYV.II6TTzvXU1H&P8{x(Д1ng38sƝo|[{L C3g F19dFղPT]X\gc|bIضp$#`8J&#poLE Q)~?~Ȃ 7)'N]aa=I϶#oR;"tU*_K4cr=?{˯?&CZ6?ﬣ/K3Vǿǟ'Ữ3(J굷{|l}J |G?̓vME2 L<×/~|ҧ*06ꆻoӪxEN?\xY=xxrk?+}6Ee2>S9tmtr&6I y3{˽]WNUJvʧZuh5IG/;N,\{u=cvc};wA3;߾`/ ,H5Nįv%oH["\mRA^Nj+j4V5dR V:Ebi ۳U]|Ej+N Xh0$ 4ܾ3ߑQ &HWԎdoo `tPٴ!TMAd\+?>wVd2PSpOw# X4 2jj 4:tO,$Sx<>C4єᭇ:c޿r?p)D#=pó/h} =|=î?GۖW^uW/LuA$g>$,]?}zK<5zö?޿ "gr,_4o  ʖ/~ɢ7z=Y>-$ۡ"r]kTO| fxP7{F9v pjbK䌡W[V |3#9CTz,1|՛o?X = v$G:x])g/ *exfM^umx5H8̝s;e #J%[okoYܖR&Es~zWQ"rMI[??xf<[7ދ>ﬣ=|.8.%F7o}_r -MP:@-| v=oUǯM7_uYj,7voO_֮{nO|~?rpRcG,;ԌA0!ktݔbFRJ*z:43 i ߃N};tl3>ؙ8ϼ3wuC6T3"0WTe0+#%IEQ$WXZßx[.捇$N4Mv3a5T<)i;Y6rHZmR*kt$|38\W~>lKʿY7p~2'4%<wz"︉*l~wO}<[zOە\`pY':HGmw>|YmӶ 0=r90D_cLa \xd}~(ϟӷߢGÖt%! {U=LTzxbny<| G-;p9X8ww7Wog3Ϭۺ~H%7t?|ެ]Yz"p΁!H*W>uGA(HWib0r 945w0vzfȚG֎mfKI~_@UtjI|F"``!K p INvޑ St5Еk*ܿn2юvNG79=}bipHDS䲶İ貟ۿ@IB~:Ec G0G:k: 2R際ùV]q˛;jnidL(岍 P4JE"0K؅bBT,Tͦw`Iߪڕtt4$ԫ`௛o翾ө׾7v _>vdkOxx7K7UUiUs[FV=n#gJPyE^"cT"tKglnoW2s%xꌷnO;|*gu`^-f6lʵzaLe,q9CMS>-7_:;wJ&a#G[+*XN&_*jcB,UjBJ ļ=}@84+CùGynsC7J՚i6[ lڒHv9x48{Fܙ=˗̚;; E@־ GZfrM{깧nٲ}TUkٲLe;ww Հ_z(Kbsgv/в}]D"`;p{xC)*՚Y3Lll-w szR*uAvBesCO-+F4fjY$N3MSu]3:osp*M|{0daChgsw~k?>j4ʻSTXUEEd ]Ld z242#r9r߯d29tN?OET'ёV6ʦX& C,T1{&nx'8-VAZ$BY9.'x΀E!po_Z&Cfvut&l1r3 Mm8W W̳~V^q[;fNaŶT2kQof&J6 狕#T5=LT,$jtr⑱h0вO~y{P?O FAUURP )]l(=m__wDoC._F2GCc#l\,*Ն;HNZ0BA_2NÝޮ`_#Jv%-YJ0 1|]k|Ƀ5M/?{ɇ:NB^.Ɛ!ko2!@T͗jjRm&6[e9}p? c@?YED$iD@Dl}@$ܫ16yh?^m J,뵆)t0[  BA"FCڽ IJ@^KXH*BV7˕eBzk@(OáH`d_j}/c{|ڕ}jD{!e dT-kZV7l9Ti ߧz$Eh|^KdBDiCRo_/:< >HOTHXiф)l"I CplFf)%p膦 iL\JrHA CHeqK"7rK09wIY8T2M M36oԮ&{bz}KxK`;>/{hRJRh8"ck8 )/~uէ _\?w1v }avz4" 'SDħ# lP,g3J,kZB~MfLph2CSN<* +SO2Z-/nV+:7C8U(3B[|]CcYvrG8BXSZ3ͦi>EaUUU45ARz|K7~UUo :g̅)tWumSp"w'eoAOv!E?>)]Kyn8C69޷Ɠ{+g$Ct5[$ӚQCw̡5O<-۔Z,su(VJ8BJ{nr K.~=Z**C-DRHLA p6k6hh}7ՍU ZSDEQ9-q)dg{ =nvv@ܛx_n/.SMtF ,9l#gϊH`Z5UTUSMU|>_05PpT9cHZ4M MOJI.O"0~kl)Y}7|?ĥow=C_v0RJ)h[bo%Ҥ^ѵA 1d!2Zik &~w}R:-l !8~ PW:h9}?×sY4w`t*"!ϧ+ R:fi4*Uӫn4ç{c[PJ8-iZNjO$kvlq?>ɏ}VBHvxv+wcԲG)[UMm$$2@V)6& 't0q뺇slVP) p M/,$pl"A$$$].u"0Fڨ7rIT29"UPYfI 4Y.J2H5Yݑ)P8XiʦIN0$rGJ9IH@)4󕊔ҧA]7M努p ~#rΘ骢:*j+L7T# Ghkey}ҏc?qrN$kN皏Ys:8dg.O¦[3E{p|=#m^ DMes~ a3g|Ѳyg;-툍#qĎBH7f8+|?=9xӎ?G9ȍׯݖɔbvDTUq h99/ŏY'n_^UPi6,F2bh-Wn5ZNVwk_Cs4m6Cډdp@8@-KMYQ6ۇ( `ڜwSrdYkO?YfFǬmk֙k[KjRLfi4ǶҶQuDlh˸X&r2UM[ل8`!8rD$)Tg/Tq{$gEcCw><}x@WG0n 3|\Q97Zc\P}Z25cl0-CRh4[*Wj9c!g\QW ,8 [#lL?Ħ<fJ!n3׎a}A}F[ іrnZ#f=ÓJ]TR/D0[;\uqaX.V=98)ɓ'OEYg?7/뎿W9 8;S,[uLS4v@n#D$)HA¡%MSjN,sm޴e-RU!"2 +/r6qY[{]~{w]MO<ѨօDamȺ`$-[lesK0FVlK,nv$&$1"FCK̳Ùgͪ% % rDӁ='hhy5+oA˗F"AD(phg3uv}iZZCSYgg"t8mI)NUFC/~|> b;r;~{O\/0V+5ź9ϘhL**4=&~-hc LDBD[V}x[/8'5S8lkhjH+!"t=Afw~;y#5UqȢWԓ'O`O<=a{rD;zz M ~L W{ߠzw'ѓtÜECC(!cRQdĈqqHp9ZӺҎ$ 0˟}Jf]~ƤRh=wF:;Ή]]Z0]glx8,ݎul˲F(|!?:^f&J\ZC[KhKi#b<Óf!FEDTF2V>=Ƕ?S"uӪp|_0(M)PPد*D1&Õ6M~O=ce}SOo/Xg x\id3BVlTEKbP3I5Mb 1I(bf[=~Q/^j׮8 ӴN{Hӳ#D^[k6l=>jJ%cu!IGp>3ttC']\q $:yӜn=c>?S/y٧v͏>̫`O?顱:t['9`q$Q8$'NOk3v8r* "0&A0@Vv'z'6=*dZ"JRXTS( ~X7XP.̺퀪pDP$N/] "܀48ղM2kfZ*rPl+J2HcHW;x,`+嫚΃2ehd8S :{/I:,Nl_vt^i Yo4:!Є͖XBU55 @ MU1D Gdz?U':s]2ͯ}hш /u~Q7jn|X4ۗJtz`(u1/=+VO?h4,L~Úed7Lk]7 NJ?XHǶ jn5[㣹LRifjMa6m(4EUMc~7t7H2tWD׵ZىukZ{x2eJnR-u[/UĴN[8RbN%gww!zQkV1~a)+B@D2!T24Wr]zaT$LJjN\/ىR&S+s顐?$RQçj: Z5mfrP:{f5S`׊4g7޿ꩇ[)V¾eK +A:W"Qq-F@QudRch(2T:֑*4ipk^S{k}O~멎z'O<`p]wd+4um 3<\.kʺ:|ɤ0A0@ mz⯫?n8ph9 {S=}F(l\W!HK@HcfFG&n۶fMadK\ hݱhl!\< Ǎ :ӑX/Ù¹˜@D* 7B,1"ɀiV8-ǪmY$9H10ݧWuP}:(ngw[_y'Ο%t60Ȳ;:|h8"2j5\:6R*jj~_`Ǒ/&Z, 082d%c<#ܘ׻dh$8 JH0B Jī+3ZVlH#d5: N?=}'{rg?~.7ד'=y{(deԑ5#FR#z ,AXYۇ&o{zГoؚ/ךH"G0Ek W58f)Z:c[$eS$Ȅ?ra̙ѹ`N*1 IIā1+xv污LQGs.Y2dQgG_T &m'q^{8Hkl^Tu'I̙Փ҂ W$K(lN&SzfZeȡ.9':|S؎ϯ:Y-ǶOA]7T%I`@(ݓE!s6'ZVǑh[N8q+^zI wI7WoQUo\-VKEy [ZN,-㱋}GͻkS]vT`$Ǧ܃wۇ2>E_<{Y} +k\UO(x\Q2DR3jv0OuFaV_$.yY\<+[o=yؓ'O{0+M$0D՚5iR!`H`ӨfV;.|bbn4MOh'VO͝3p~O8u ID3dMre[O}{>nдVSݨ[5Oo} cC5;8Hmu b3ٰ-Ϭٲq˶#h4hE ;{aۭߧ!iM|~9ӈQ25 frHw ,GV+bnv8l$;>9Uǟ𳗜ލo;ys$]s'?pFc9x|k{߰@ԾO3kU{Wzԉˏ?y8x4>s΅l[MtsٳgΊ'@sΙ  * b*(5vvV-[;á 8`MA <1f댋.[К<8 ,[2[z7nF$IJ! 2d ٲscO=e|`9v<9{`p`F_8T2&U+ Zn2 Έ$0 9gvz^(D$CUV8H$LJBxpO:\ N?;-$m {)ZͦlJ3tw͟;??S7{s"իvԉxWW W G#pVzt~m-K©yϟE :2B_1xCq9k4x\ݓL1'i"턻uU?푳Ey[=A'O{`=~"qrܦf/Ā 3[vf2:Zhf:O kg 3%]ے;ΊDNo(zͷ$x7?h3To9ڴܶb-wyݽ@$7iT)7nY~cj:`% gL23)>_OwgOO# 1)mI;l$1F*go4qhf"/V*_AǞ) =𗿢jn7C +]|5_n;/`;; vQ" ckfPu.$Bdi9v$x0SU (Ȭ%0>OmZ~(/;#-ۯwɂPDEeF2ͪ)(3 dH !ʑS -ZLղHL'NzFձ\?} o_tٝt#bz䩡'6>^ZfCԫ1]骪={s?Tuc^?qO_⾧g~B,+O^ܙqqgG7=r^~=w?g-̙5wdg:'q|h)V2&J4~bM2X2P56ɫ;1ʠ.~SNSq`DTuGpܛV7_7t?_Θ`=y! X2%\]q/#0@frPߴ=v†_"0UI'>aܻb[>7{,INg =rܙ|a,׌[i6?- |Sdr޳o>5Z,C/[`fwo1)neD PhD*?vesd4OٺiӈrK:QYpI HdC"sZ#F(ބ4,rD{!:ʕf~),DR3k$'"1I~ 2o\qo>n^C$PYTlf! x櫅B.dyi;U< vSX|O_uʳN?! ?%2DF ukXk r,^0oT2!Ip4 UQg\ՒL3i0T:Ot]}/J)m)X>M`Ȟ_?nzɃUU GzG$Mc#c ѧ{ďK>tEo:8uon^-@J03{p¹]c frA"kKq'kq"H :6UJD^#1(1")llXTR"M9ܓ`nCjmUñe(}U rmۧX|O{ /B_L:DUKzTiVjͲ,Y5IPם|ÎW>ʫ?_an" @F\6 Z'?pάE RRH!?D}UYhV:LƌP|>@ 1ۂl11=x2 iW"0"W|MħR$%/6 uΧK|>C#=y`O<0%#,$" m&kM0]z0 ѰG'*°3*#m0>vnx<>^eR&nOY?C$qѲVoY i˄WhoWO,1͑l!WU+=wg;N@hN#r.fRߴi7l8+V̖jj<I&~_Tl(>s;rX"C]5  `S!fD@&pll5ڐhIHZ(FOw-wse,[O{^䲕 {6+iTMDx$:BT4 qdtzٲ/UHnj2F7<'hk튳N?>‹>L{υF6l^G]W,V>sL$#GJ)54qE|#3^:Q]g@OH,[2c`4Է+I/ {:]ş׿ݞo}o;i޸yx ̛97SaXUU2PɯTD G *Cd ŢWV3 ttDtNw7ھEj>WU:, `7D _o s7o{Ƀ,/!yɓ{Q Ӗ 7ljRM7o0|@w4 :)'`M1"6bx87:^RUM> L$`X4r-w<}X놏z"G.q~gvݒՊsɗ~֟{I+y|h<8SŧH# ab>[(T]Ubp3FBJǦJ۶mth8[*7lGD¡D$DXVkrR*MBȞt'~j fj%`MST}>(5t+%2weQ&S)pc1zC6Ll `4~>aN`\oN9mɥ0-}׿휣.!)AeRU$`e5vPgFF m +X( vX< F+*CiKVZsUuxN/͎͆G*=fG2?wAPmG74XtaE/!pbeDz2 t@d]§Ѷ|u#NG&B#Kӿ-y]_]]nK~'_zտ2>j[{}#CqS"‘3dLꚪ0n"`@ i zT5'LUHJŪ5*IMlf{w|xl{2wstܢR: !"cɂ`YNcB>>:Q,M"⡀'7nNSZW ɿ%{Zy60ӳtH$u)%0 k㱈qiB(m+%11Qm6D,Xۯ ]km;/JVՙ&Aw"yi˰Z7='Wo~Ƀ;})oM/ W|^'m8/zwo:T@.Aq> zΌj%S|M S+ N6REi S4#آY F˲l!,[ *O?t\>a6IE'$ȏ|i)izά;H-u_d j6VMn#J4A#d}2l׭rMVP5B XE72BmY.Wn;yA\կcZj|O^k3N=& Jdx50Ƥlx5۷s͞Ƅ#Zf ST=h(ᱨpǑcb &ƋvӎDWqrָ&(_4GFʭf# vuD~F:.Umlnkܓ.||=y`O<x`4j0"I_qGU+Dt8 sa)I%b31VNT3 ((D>zϥWؗH<3 ۀB;<Wq3`R |IBcL j +{Wc;" jَ8cK N{*/[yf~WwOJ NSn`1[JZF2u uER:CHϹ LiD**UрF $#*zXU~wé\h~5kĢx,NzSH"5U܍QO$I"ڶ([7g~8O^{N9*<1&%TydUOo6QHYp^27t!6-+a@Tb,n ǁjd*DczgZKӻy@Tw^M67Tjh^`wg8c$>pl [k"{aF@N$=~177{\ǣoE3Ů'O{ɓE<Y{Oj:88~y &IPG+LA7d*ju \IMZg\ jZYJO|뺭#ht6 pTUXp(tMG'<ٍ3gϚ;~4C)t) Цg7?cɂR ۱[f4[VlZ޲Z-l\r~C;ߗu]Q8PI$%i.Vr)hJFÑfdq2ȉy( P@Q[ HRP)h7K喔,bz"WcTbnʎdr.t#6ЙHFbSUUAO0F3ӴsM15N9* .{H@Kb[ I ҉eK̙97tMWUMc\h3&bG5#BnhL#Jesb\B<L Dݪps7RX x$l "52TR)MS3{z[j_~쌷}ރ 9?o'm !IW퉉DW{ds$ڠҝHDD $1VdѐlT, *eshK6W(7Zm 4V"ᐢ*wC`( `\᜹&7( O>546|d2#8th^H$[_"% cER Qo5VPjl6kucޞH4EP4 uCS9WѰ jijEVjpB!Gڈ!JC`XQUpELū$omݒa4I(o=uA|¡-J3+fm6j i;? twc`, 5ιk+Y9bI'-_r>t)GE">hWgBD$YZn>fݳF#O,pf@`-jF3tR X$DY`frF ]HXswh*Nwf`S;DwIQȯH>/]n[Gjrxs?`=yUIL2t$[h91ܨ/R4zO_4WۯdHJBB0LV6T]Gr6mޒU*)[ܯAr#PwW+g m# $ґȹiYccZv[BƘA#s p12Dbqΐv(h6F^Wz>W6uUMLU|P#9s9`XJ$f be C~oPGJGAZ-+),D`}h @h["_hV AMSl[Jv :e{z`z;5/ݓ;?|a.c#\Ш6#SpWG+K!MU΍>邏\v 8CDPJgXuZ#1gTwOwx $ RәPNT"11KE{|֨;D  t&.N-X2zRTL1C.u NvŽ/=}w|eק}Z%>ogc^ד'=` ]iDI2ɛ6k9 $Mt *%3rEOAE %DIDD$%BJdPZ\T)JǞZVlͩ x$J"xDfñ,ȁqH$P$Iq,nYlZVѨkMƜshrlqm;R dqk+ ֮n6[P  A g膮qcaBF(h0T*dr'jOGǑ`h, ZV4% i KG0~Uϯ6G񱒰e"Ou}>C9۲-Qt6n{ZfoضPvǺ-;ЗHBsfU(T'&[7lt#cՉwV޿k:##anP jCwx5ͦǻRtu93;j<4'f)'d F܈CH(#J%}A?)D}b=h,q3C`4Ñ_;3mLJ2|皗<1v.yG:-ȓ+U<`Y&T8 s\Jl6|9>Zl`HFc@9@R B"2! [ RQw̆;\㧟z $Gߘla9BeYNlZUתZ4?fa6-؎#p|}sg͚ 9 =fذ4|F ` U]T@5sЩ;z<5 ?`wg;9Иm Ђkn,Θ#`*P@FcʙFv{"q&)qX1?:#D8͍JBR`M͙5cX"E|_%jul02/yn6.|1xܿ\PB&[cV=[ҕ7sFWg2j:\q,fl>o6[!3;fvvE7ǒrj9Ѹ+VnSq_*B0(adrX9ꖟC|m|XN_w3VURk1`é._z:cjg^jT+F|ALiܙeՆlhz UD,J{Rt") qVr\:狅Z0¢!39;t܁doO,_w9b1.G>>4lYs ]4\7"X'Y0 H!^̸Y,V~_Ow4VgϯP05WyӴ3x,:P@"Ci*N'>UR+k;??l{oz<ҫupe`Rku^ŏ zF!$-͑$j% I )  RZ(WljJ(GPeT%!2ƹ:/|Whm[F9MiuvgTxv./ߐL/ :}ga2DnK8@Dȁ(e՝BEqmIU~׸9\BLkt^4PWW$m;?1!}M7n#mD2besE |Tb#s f|a,f+ |JW:2k0=;'W2C fȐ8` oT)*>LnjTNsC׬ސcD#6zB ~O.M^ד~"v_<%{OO6&Hf p 6B$De#I9Z-7DIJSaN0RTZqƑ!`2}WXߌnI`X0~#l:#珀rZy9@KBv_Lo\,&riCScHD蠞tDRJDy/ݩ~`\~C|\WIWQRe(99Wku+u#wb!}w|@SFىf>gN$ $ɦ&VвEP+L#@ J*Fl˾NM_OWN`x~B,_K&O<6-5/[@J3'O{+9kDLl9 IDd[0z URir XP*ZõqRJE2py25E oGlC,,YRNwtW!sƸO4E*Wa,!Ħ[w(۽Vn\#ᠪZ0[-pGH C`H H D@*_t`oOHI$%c,zw}0 a~*N#24l^.5|_Gg4wsUï lYstR*zPDR@!lԝFST2[*U\\Lp4N]d2 h6#HTkZ9TMHQgRK1j91ZWl]SSp,S #{؅ѶT6GFfÌ=]`@C@ 55E" }u̵m?!_X$}p,/Kn5wdw]$<`UɅi*@HfIjZ)VJF g>Crr!m'ɶ,oZ~ӍC#<\BdҶ[۶}@Rm5pU/EP8RX(umGA(8r;0D㿿>zBX-e;2_(j^.WJQf$GRH $q6{`9 @D$Ύ<3<C(A>4Ec\눌5jD>_Ww<#a?XD@8͕焍pG:v2p;03 \kN&e+)grz\irf;tg:tttR*Su<8#:H8 TUa "z<# 2_ZÞ+O4|uEab*eΪSv Ų923kD<"InsUU %SSHvUxz\?ʟ|fӯ~mw>͗[ch[?r?(y cLa6>fÕ鬮Τm~=yag"hg_%$r XKȦL*L]e!,,eH-K] #dw>nIUHZWCpT3)*rcp4x7ш"@" a뚶nq> -0v)"(z۝O]y~s9!Zm 0j5[|P*Ucщ ٴZriU 8㎰p*Ѕpۡpih NEk,Ue6vP,YCz橮Qr_AJ*͡hCbJˍBe4Wu]3DOgj?(gO<55‚~GT\ G `6)Wr8Vg*H4t͉yoMJ:ZhHDC~$.6@e*R3[%M`Ob\{z|JD_:3( H)MZ-@CS8Pϕtw?vv!`َ,cZ B#IVu3 sw4Z6?> !H=yɓ'={MC>.k!H M=rf$*EQӴUDt @EQ O<3r.'"X$LD:⁠RJ @@ኔR8%Q{og>pсRn k v_\{9cDRJ04VB`ji6뵦p0 Jϥ$UAGG-?wNDfVnwUw; BŃe ƃf OYH?5;w8H_=֢O>UI׷WAg8>iթºم6{Xrr|بTXȒ.ަ+S2wpٜ*?f6.^E'FX7 :/^ufn.jE^^߂D4Շlme4 O$W'ⵕj5dũY5% J*bLu:NGդ{;L:Z98lԢ4M"}9Բ%;5`!LTTfӤ{;v2L|WQ\\O~?w_?;MZRo$*v{rx7mjE͊4NR鬝s.5O`ry>đ?9iףHKS4o?𪢒&덟=t_>=lkxo]^b:$72'ǭGN$fJB/RϬ"6q i&d8Lz74Q ['ժ,FoaQ?y[s^{xFIN,[}0 2Ho*ol[<ײ,Il6IGzx?혤!MMĩ:fbR,di:u!".M4a0w{j7[{_}+p8/eL\'Wooje:kzWI6NTJU,TUE.FlncgAmӽvgSRvh:N?o `%ܽ7UwR 4m 8;,h0w z0M , Y+BBVQzu};ͪx#|~D$X"&TZ\.ίp4Vޭ:M+*Xwv,gK}W+QRߛًn~kV%gY|~3N{;Z7\+4NT<\#g#h>>`gNB2v5cGNxlS*sg~doWUsŽahGov.8& `mXWg}kxa4IF lZj48`8%&N?w#?g?+I94r"i2덺Rÿo/7QyM+V^S'iE#H"O0L=sOwxPcgbm*"ib7/.F~aPfiqI𝭧*"wyq;9=}t܊b_\KbSf$xy6rnڻvͷ;Ϋ8ɇ]u>^wS:LJtѮT$|[ZJ#v `,+rotI>CfX!M'2h8 L'iI0{E{8`2QW*?ѿEZE^#1 Fh05*ՈZh'JW8rQG>ȒEh=uFq'fl컺7PBzxf25[Fu~v|f8 [kONZG(bx ^V1__]tURDl9ycݛ_^""R֪悉mYxZWxcߙM30LXf.;-4O$`ӑL&td^>ISkԪdu#?9iﵪNꎟf?,jQ{;t|uJl*j|ZuYh""Ήh\\Eޝ>?Dm]DMfYg47?ݭ7*"l&[DW˳J%Z. "L"JO967WWJÓJͬZQ寧/j[qZxs޼H:XOK՜+d"q 0L?0{Mxy1?o}qª?;ǥkHV$Mx\4xm5N,ףHEcI27A?j>vv{qq꫱/=^zcu8zS[ň/g0%7Wg$Iw=m6bs;IE[<"ED nyS/|$bӒyM!k~MZԹಧt _[ZRz=Wp~WGorDS1&?.~v;fN8U4ITdf!I-1K,l!3\68_H̉48I('sf:/^viZ9]Oбd^\ _GG{Oo4"Q]ʃ[t{/;z5~`'+k/JH`^t^+/Ed^_t//:Q?h>m+J+,%¥tzggQ.2q\-rBX?yfXT|::XkvUsNH63:qNSsNfQ<_hV|*@mt"xYo|g_ŋ0 `bE 9ʍ7(m_P2b'wdb. jQdA$Va,I_ ^u?}rppI( J p~4N'ݽ^m Β3~u~;Ow[Ul_0]^<ʋuwWNӓ݃jqP5DzDռ2:I3tAm ok?jUbgԊUsML k痣&W99i֪Q1]`R<1Q ͝GfUv[4UMxTEgUEt3z.X3U5-Dx^, ٴk֋U5qhWES]"` 3x1^oRBz kc3]l YD4IVIP !;{YSԜ`h췚qR1[WUsJv-QѠ*={v}l4.$Y.+vKs"".r9K_'jN_ G׵ͯ;M*wg( @G ;1-/["UsןTuj{$|sv"t$;g8 BjV^ X]Uq뉳a?9U?ŪduPH]`dW.Ow ` य़xw aۥˣn\b m!|k(rE_畮aQv|SK'%cN `V,(48ri+j9mY n1sئ9?{}) @?hCٷo|f_ٚ?MT̅cM?Mst.nZ-ۂ`/1',g_7ϻ6/-l+ZvH6D_]g6w# \ l Xi^]md+)-/ɛWw9ݝmAŕbbbS5?\[Y*Ƌu`-zҵsw%]0nP ]J?ZDGYuk&?tq6+yk,ݛϻq1rNvY>d'5I5`|ޭQLڼް[r{ޚX6aWît޹г,MӺM8w_+w2 %qVG~ՑMgN噵g)/+Z\ئ)W.8M 5.}O/|ij0> ^Z\vU1bޚݱs2Oŵ鶓_osN3/|:չp-fϚ:3s(xA峂mKi.F-ULW7-xد^kzKzM;y]0/_=~Y=*: עto@ˋvL%}?2F@6ƎosC{Gب}M/`KODmݖb`Lg>4j,wS5] |({]m[qw.._7{{kvkn:aOfokmiɝ,z1ύ-߿>[KLu\~۶ԯq;_bf}/ \w~ ߿4`.쏛 r%Ħbe+ _)g3#P)_TPa'Ω:1s> *A|e} 7-G/? V׽La7xf._L[n(uio׾yHwr>hL~ԽY?_YޘގwU;crVn^T^)m~M1W4競\mq+TKm8Pw?nhoRQyk/nIW-^qnVٴ=|rO~In`9;t㉳K7-:Ebufwsk3r?.< 7m-]bGCS͏Ɇ,.fvoK;-gbOL궄ҫE/dۏ'Uwli?&NTv8V_As4X,Ux0L+G6@EL,K)dRX)qfRu֯_y ]Plgio}q'Z`}݁mwSۘny|a_z_޵](˹Xׯ[e#Y~Z`ql]@m'````````                 000000000@@@@@@@@@`````````        9@@@@@@@@@`````````                 000000000@@@@@@@@```````_9 .IENDB`cyclopts-3.9.0/cyclopts/000077500000000000000000000000001475451620500152345ustar00rootroot00000000000000cyclopts-3.9.0/cyclopts/__init__.py000066400000000000000000000031341475451620500173460ustar00rootroot00000000000000# Don't manually change, let poetry-dynamic-versioning handle it. __version__ = "0.0.0" __all__ = [ "App", "Argument", "ArgumentCollection", "ArgumentOrderError", "Token", "CoercionError", "CommandCollisionError", "CycloptsError", "Dispatcher", "DocstringError", "EditorError", "EditorNotFoundError", "EditorDidNotSaveError", "EditorDidNotChangeError", "Group", "InvalidCommandError", "MissingArgumentError", "MixedArgumentError", "RepeatArgumentError", "Parameter", "UnknownOptionError", "UnusedCliTokensError", "UNSET", "ValidationError", "config", "convert", "default_name_transform", "edit", "env_var_split", "types", "validators", "run", ] from cyclopts._convert import convert from cyclopts._edit import EditorDidNotChangeError, EditorDidNotSaveError, EditorError, EditorNotFoundError, edit from cyclopts._env_var import env_var_split from cyclopts.argument import Argument, ArgumentCollection from cyclopts.core import App, run from cyclopts.exceptions import ( ArgumentOrderError, CoercionError, CommandCollisionError, CycloptsError, DocstringError, InvalidCommandError, MissingArgumentError, MixedArgumentError, RepeatArgumentError, UnknownOptionError, UnusedCliTokensError, ValidationError, ) from cyclopts.group import Group from cyclopts.parameter import Parameter from cyclopts.protocols import Dispatcher from cyclopts.token import Token from cyclopts.utils import UNSET, default_name_transform from . import config, types, validators cyclopts-3.9.0/cyclopts/_convert.py000066400000000000000000000407671475451620500174430ustar00rootroot00000000000000import collections.abc import sys import typing from collections.abc import Sequence from enum import Enum from functools import partial from inspect import isclass from typing import ( TYPE_CHECKING, Any, Callable, Iterable, Literal, Optional, Union, get_args, get_origin, ) from cyclopts.annotations import is_annotated, is_nonetype, is_union, resolve from cyclopts.exceptions import CoercionError, ValidationError from cyclopts.field_info import get_field_infos from cyclopts.utils import UNSET, default_name_transform, grouper, is_builtin if sys.version_info >= (3, 12): # pragma: no cover from typing import TypeAliasType else: # pragma: no cover TypeAliasType = None if TYPE_CHECKING: from cyclopts.argument import Token _implicit_iterable_type_mapping: dict[type, type] = { Iterable: list[str], typing.Sequence: list[str], Sequence: list[str], frozenset: frozenset[str], list: list[str], set: set[str], tuple: tuple[str, ...], } ITERABLE_TYPES = { Iterable, typing.Sequence, Sequence, frozenset, list, set, tuple, } NestedCliArgs = dict[str, Union[Sequence[str], "NestedCliArgs"]] def _bool(s: str) -> bool: s = s.lower() if s in {"no", "n", "0", "false", "f"}: return False elif s in {"yes", "y", "1", "true", "t"}: return True else: # Cyclopts is a little bit conservative when coercing strings into boolean. raise CoercionError(target_type=bool) def _int(s: str) -> int: s = s.lower() if s.startswith("0x"): return int(s, 16) elif s.startswith("0o"): return int(s, 8) elif s.startswith("0b"): return int(s, 2) else: # Casting to a float first allows for things like "30.0" return int(round(float(s))) def _bytes(s: str) -> bytes: return bytes(s, encoding="utf8") def _bytearray(s: str) -> bytearray: return bytearray(_bytes(s)) # For types that need more logic than just invoking their type _converters: dict[Any, Callable] = { bool: _bool, int: _int, bytes: _bytes, bytearray: _bytearray, } def _convert_tuple( type_: type[Any], *tokens: "Token", converter: Optional[Callable[[type, str], Any]], name_transform: Callable[[str], str], ) -> tuple: convert = partial(_convert, converter=converter, name_transform=name_transform) inner_types = tuple(x for x in get_args(type_) if x is not ...) inner_token_count, consume_all = token_count(type_) if consume_all: # variable-length tuple (list-like) remainder = len(tokens) % inner_token_count if remainder: raise CoercionError( msg=f"Incorrect number of arguments: expected multiple of {inner_token_count} but got {len(tokens)}." ) if len(inner_types) == 1: inner_type = inner_types[0] elif len(inner_types) == 0: inner_type = str else: raise ValueError("A tuple must have 0 or 1 inner-types.") return tuple( convert(inner_type, chunk[0] if inner_token_count == 1 else chunk) for chunk in grouper(tokens, inner_token_count) ) else: # Fixed-length tuple if inner_token_count != len(tokens): raise CoercionError( msg=f"Incorrect number of arguments: expected {inner_token_count} but got {len(tokens)}." ) args_per_convert = [token_count(x)[0] for x in inner_types] it = iter(tokens) batched = [[next(it) for _ in range(size)] for size in args_per_convert] batched = [elem[0] if len(elem) == 1 else elem for elem in batched] out = tuple(convert(inner_type, arg) for inner_type, arg in zip(inner_types, batched)) return out def _convert( type_, token: Union["Token", Sequence["Token"]], *, converter: Optional[Callable[[Any, str], Any]], name_transform: Callable[[str], str], ): """Inner recursive conversion function for public ``convert``. Parameters ---------- converter: Callable name_transform: Callable """ from cyclopts.argument import Token from cyclopts.parameter import Parameter converter_needs_token = False if is_annotated(type_): from cyclopts.parameter import Parameter type_, cparam = Parameter.from_annotation(type_) if cparam._converter: converter_needs_token = True converter = lambda t_, value: cparam._converter(t_, (value,)) # noqa: E731 if cparam.name_transform: name_transform = cparam.name_transform else: cparam = None convert = partial(_convert, converter=converter, name_transform=name_transform) convert_tuple = partial(_convert_tuple, converter=converter, name_transform=name_transform) origin_type = get_origin(type_) # Inner types **may** be ``Annotated`` inner_types = get_args(type_) if type_ is dict: out = convert(dict[str, str], token) elif type_ in _implicit_iterable_type_mapping: out = convert(_implicit_iterable_type_mapping[type_], token) elif origin_type in (collections.abc.Iterable, collections.abc.Sequence): assert len(inner_types) == 1 out = convert(list[inner_types[0]], token) # pyright: ignore[reportGeneralTypeIssues] elif TypeAliasType is not None and isinstance(type_, TypeAliasType): out = convert(type_.__value__, token) elif is_union(origin_type): for t in inner_types: if is_nonetype(t): continue try: out = convert(t, token) break except Exception: pass else: if isinstance(token, Sequence): raise ValueError # noqa: TRY004 raise CoercionError(token=token, target_type=type_) elif origin_type is Literal: # Try coercing the token into each allowed Literal value (left-to-right). last_coercion_error = None for choice in get_args(type_): try: res = convert(type(choice), token) except CoercionError as e: last_coercion_error = e continue if res == choice: out = res break else: if last_coercion_error: last_coercion_error.target_type = type_ raise last_coercion_error else: raise CoercionError(token=token[0] if isinstance(token, Sequence) else token, target_type=type_) elif origin_type is tuple: if isinstance(token, Token): # E.g. Tuple[str] (Annotation: tuple containing a single string) out = convert_tuple(type_, token, converter=converter) else: out = convert_tuple(type_, *token, converter=converter) elif origin_type in ITERABLE_TYPES: # NOT including tuple; handled in ``origin_type is tuple`` body above. count, _ = token_count(inner_types[0]) if not isinstance(token, Sequence): raise ValueError if count > 1: gen = zip(*[iter(token)] * count) else: gen = token out = origin_type(convert(inner_types[0], e) for e in gen) # pyright: ignore[reportOptionalCall] elif isclass(type_) and issubclass(type_, Enum): if isinstance(token, Sequence): raise ValueError if converter is None: element_transformed = name_transform(token.value) for member in type_: if name_transform(member.name) == element_transformed: out = member break else: raise CoercionError(token=token, target_type=type_) else: out = converter(type_, token.value) elif is_builtin(type_): assert isinstance(token, Token) try: if token.implicit_value is not UNSET: out = token.implicit_value elif converter is None: out = _converters.get(type_, type_)(token.value) elif converter_needs_token: out = converter(type_, token) # pyright: ignore[reportArgumentType] else: out = converter(type_, token.value) except CoercionError as e: if e.target_type is None: e.target_type = type_ if e.token is None: e.token = token raise except ValueError: raise CoercionError(token=token, target_type=type_) from None else: # Convert it into a user-supplied class. if not isinstance(token, Sequence): token = [token] i = 0 pos_values = [] hint = type_ for field_info in get_field_infos(type_, include_var_positional=True).values(): hint = field_info.hint if isclass(hint) and issubclass(hint, str): # Avoids infinite recursion pos_values.append(token[i].value) i += 1 else: tokens_per_element, consume_all = token_count(hint) if tokens_per_element == 1: pos_values.append(convert(hint, token[i])) i += 1 else: pos_values.append(convert(hint, token[i : i + tokens_per_element])) i += tokens_per_element if consume_all: break if i == len(token): break assert i == len(token) out = type_(*pos_values) if cparam: # An inner type may have an independent Parameter annotation; # e.g.: # Uint8 = Annotated[int, ...] # rgb: tuple[Uint8, Uint8, Uint8] try: for validator in cparam.validator: # pyright: ignore validator(type_, out) except (AssertionError, ValueError, TypeError) as e: raise ValidationError(exception_message=e.args[0] if e.args else "", value=out) from e return out def convert( type_: Any, tokens: Union[Sequence[str], Sequence["Token"], NestedCliArgs], converter: Optional[Callable[[type, str], Any]] = None, name_transform: Optional[Callable[[str], str]] = None, ): """Coerce variables into a specified type. Internally used to coercing string CLI tokens into python builtin types. Externally, may be useful in a custom converter. See Cyclopt's automatic coercion rules :doc:`/rules`. If ``type_`` **is not** iterable, then each element of ``tokens`` will be converted independently. If there is more than one element, then the return type will be a ``Tuple[type_, ...]``. If there is a single element, then the return type will be ``type_``. If ``type_`` **is** iterable, then all elements of ``tokens`` will be collated. Parameters ---------- type_: Type A type hint/annotation to coerce ``*args`` into. tokens: Union[Sequence[str], NestedCliArgs] String tokens to coerce. Generally, either a list of strings, or a dictionary of list of strings (recursive). Each leaf in the dictionary tree should be a list of strings. converter: Optional[Callable[[Type, str], Any]] An optional function to convert tokens to the inner-most types. The converter should have signature: .. code-block:: python def converter(type_: type, value: str) -> Any: "Perform conversion of string token." This allows to use the :func:`convert` function to handle the the difficult task of traversing lists/tuples/unions/etc, while leaving the final conversion logic to the caller. name_transform: Optional[Callable[[str], str]] Currently only used for ``Enum`` type hints. A function that transforms enum names and CLI values into a normalized format. The function should have signature: .. code-block:: python def name_transform(s: str) -> str: "Perform name transform." where the returned value is the name to be used on the CLI. If ``None``, defaults to ``cyclopts.default_name_transform``. Returns ------- Any Coerced version of input ``*args``. """ from cyclopts.argument import Token if not tokens: raise ValueError if not isinstance(tokens, dict) and isinstance(tokens[0], str): tokens = tuple(Token(value=str(x)) for x in tokens) if name_transform is None: name_transform = default_name_transform convert_priv = partial(_convert, converter=converter, name_transform=name_transform) convert_tuple = partial(_convert_tuple, converter=converter, name_transform=name_transform) type_ = resolve(type_) if type_ is Any: type_ = str type_ = _implicit_iterable_type_mapping.get(type_, type_) origin_type = get_origin(type_) maybe_origin_type = origin_type or type_ if origin_type is tuple: return convert_tuple(type_, *tokens) # pyright: ignore elif maybe_origin_type in ITERABLE_TYPES or origin_type is collections.abc.Iterable: return convert_priv(type_, tokens) # pyright: ignore elif maybe_origin_type is dict: if not isinstance(tokens, dict): raise ValueError # Programming error try: value_type = get_args(type_)[1] except IndexError: value_type = str dict_converted = { k: convert(value_type, v, converter=converter, name_transform=name_transform) for k, v in tokens.items() } return _converters.get(maybe_origin_type, maybe_origin_type)(**dict_converted) # pyright: ignore elif isinstance(tokens, dict): raise ValueError(f"Dictionary of tokens provided for unknown {type_!r}.") # Programming error else: if len(tokens) == 1: return convert_priv(type_, tokens[0]) # pyright: ignore tokens_per_element, _ = token_count(type_) if tokens_per_element == 1: return [convert_priv(type_, item) for item in tokens] # pyright: ignore elif len(tokens) == tokens_per_element: return convert_priv(type_, tokens) # pyright: ignore else: raise NotImplementedError("Unreachable?") def token_count(type_: Any) -> tuple[int, bool]: """The number of tokens after a keyword the parameter should consume. Parameters ---------- type_: Type A type hint/annotation to infer token_count from if not explicitly specified. Returns ------- int Number of tokens to consume. bool If this is ``True`` and positional, consume all remaining tokens. The returned number of tokens constitutes a single element of the iterable-to-be-parsed. """ type_ = resolve(type_) origin_type = get_origin(type_) if (origin_type or type_) is tuple: args = get_args(type_) if args: return sum(token_count(x)[0] for x in args if x is not ...), ... in args else: return 1, True elif (origin_type or type_) is bool: return 0, False elif type_ in ITERABLE_TYPES or (origin_type in ITERABLE_TYPES and len(get_args(type_)) == 0): return 1, True elif (origin_type in ITERABLE_TYPES or origin_type is collections.abc.Iterable) and len(get_args(type_)): return token_count(get_args(type_)[0])[0], True elif is_union(type_): sub_args = get_args(type_) token_count_target = token_count(sub_args[0]) for sub_type_ in sub_args[1:]: this = token_count(sub_type_) if this != token_count_target: raise ValueError( f"Cannot Union types that consume different numbers of tokens: {sub_args[0]} {sub_type_}" ) return token_count_target elif is_builtin(type_): # Many builtins actually take in VAR_POSITIONAL when we really just want 1 argument. return 1, False else: # This is usually/always a custom user-defined class. field_infos = get_field_infos(type_, include_var_positional=True) count, consume_all = 0, False for value in field_infos.values(): if value.kind is value.VAR_POSITIONAL: consume_all = True elif not value.required: continue elem_count, elem_consume_all = token_count(value.hint) count += elem_count consume_all |= elem_consume_all # classes like ``Enum`` can slip through here with a 0 count. if not count: return 1, False return count, consume_all cyclopts-3.9.0/cyclopts/_edit.py000066400000000000000000000070601475451620500166750ustar00rootroot00000000000000import os import tempfile import time from pathlib import Path from typing import Sequence, Union class EditorError(Exception): """Root editor-related error. Root exception raised by all exceptions in :func:`.edit`. """ class EditorDidNotSaveError(EditorError): """User did not save upon exiting :func:`.edit`.""" class EditorDidNotChangeError(EditorError): """User did not edit file contents in :func:`.edit`.""" class EditorNotFoundError(EditorError): """Could not find a valid text editor for :func`.edit`.""" def edit( initial_text: str = "", *, fallback_editors: Sequence[str] = ("nano", "vim", "notepad", "gedit"), editor_args: Sequence[str] = (), path: Union[str, Path] = "", encoding: str = "utf-8", save: bool = True, required: bool = True, ) -> str: """Get text input from a user by launching their default text editor. Parameters ---------- initial_text: str Initial text to populate the text file with. fallback_editors: Sequence[str] If the text editor cannot be determined from the environment variable ``EDITOR``, attempt to use these text editors in the order provided. editor_args: Sequence[str] Additional CLI arguments that are passed along to the editor-launch command. path: Union[str, Path] If specified, the path to the file that should be opened. Text editors typically display this, so a custom path may result in a better user-interface. Defaults to a temporary text file. encoding: str File encoding to use. save: bool **Require** the user to save before exiting the editor. Otherwise raises :exc:`EditorDidNotSaveError`. required: bool **Require** for the saved text to be different from ``initial_text``. Otherwise raises :exc:`EditorDidNotChangeError`. Raises ------ EditorError Base editor error exception. Explicitly raised if editor subcommand returned a non-zero exit code. EditorNotFoundError A suitable text editor could not be found. EditorDidNotSaveError The user exited the text-editor without saving and ``save=True``. EditorDidNotChangeError The user did not change the file contents and ``required=True``. Returns ------- str The resulting text that was saved by the text editor. """ import shutil import subprocess for editor in (os.environ.get("EDITOR"), *fallback_editors): if editor and shutil.which(editor): break else: raise EditorNotFoundError if path: path = Path(path) path.parent.mkdir(exist_ok=True, parents=True) else: path = Path(tempfile.NamedTemporaryFile(suffix=".txt", mode="w", delete=False).name) path.write_text(initial_text, encoding=encoding) past_time = time.time() - 5 # arbitrarily set time to 5 seconds ago; some systems only have 1 second precision. os.utime(path, (past_time, past_time)) # Set access and modification time start_stat = path.stat() try: subprocess.check_call([editor, path, *editor_args]) end_stat = path.stat() if save and end_stat.st_mtime <= start_stat.st_mtime: raise EditorDidNotSaveError edited_text = path.read_text(encoding=encoding) except subprocess.CalledProcessError as e: raise EditorError(f"{editor} exited with status {e.returncode}") from e finally: path.unlink(missing_ok=True) if required and edited_text == initial_text: raise EditorDidNotChangeError return edited_text cyclopts-3.9.0/cyclopts/_env_var.py000066400000000000000000000030431475451620500174050ustar00rootroot00000000000000import os from pathlib import Path from typing import Any, Optional, get_args def _is_path(type_) -> bool: from cyclopts._convert import resolve if type_ is Path: return True for inner_type in get_args(type_): inner_type = resolve(inner_type) if _is_path(inner_type): return True return False def env_var_split( type_: Any, val: str, *, delimiter: Optional[str] = None, ) -> list[str]: """Type-dependent environment variable value splitting. Converts a single string into a list of strings. Splits when: * The ``type_`` is some variant of ``Iterable[pathlib.Path]`` objects. If Windows, split on ``;``, otherwise split on ``:``. * Otherwise, if the ``type_`` is an ``Iterable``, split on whitespace. Leading/trailing whitespace of each output element will be stripped. This function is the default value for :attr:`cyclopts.App.env_var_split`. Parameters ---------- type_: type Type hint that we will eventually coerce into. val: str String to split. delimiter: Optional[str] Delimiter to split ``val`` on. If ``None``, defaults to whitespace. Returns ------- list[str] List of individual string tokens. """ from cyclopts._convert import resolve, token_count type_ = resolve(type_) count, consume_all = token_count(type_) if count > 1 or consume_all: return val.split(os.pathsep) if _is_path(type_) else val.split(delimiter) else: return [val] cyclopts-3.9.0/cyclopts/annotations.py000066400000000000000000000125651475451620500201540ustar00rootroot00000000000000import inspect import sys from inspect import isclass from typing import Annotated, Any, Optional, Union, get_args, get_origin import attrs _IS_PYTHON_3_8 = sys.version_info[:2] == (3, 8) if sys.version_info >= (3, 10): # pragma: no cover from types import UnionType else: UnionType = object() if sys.version_info < (3, 11): # pragma: no cover from typing_extensions import NotRequired, Required else: # pragma: no cover from typing import NotRequired, Required # from types import NoneType is available >=3.10 NoneType = type(None) AnnotatedType = type(Annotated[int, 0]) def is_nonetype(hint): return hint is NoneType def is_union(type_: Optional[type]) -> bool: """Checks if a type is a union.""" # Direct checks are faster than checking if the type is in a set that contains the union-types. if type_ is Union or type_ is UnionType: return True # The ``get_origin`` call is relatively expensive, so we'll check common types # that are passed in here to see if we can avoid calling ``get_origin``. if type_ is str or type_ is int or type_ is float or type_ is bool or is_annotated(type_): return False origin = get_origin(type_) return origin is Union or origin is UnionType def is_pydantic(hint) -> bool: return hasattr(hint, "__pydantic_core_schema__") def is_dataclass(hint) -> bool: return hasattr(hint, "__dataclass_fields__") def is_namedtuple(hint) -> bool: return isclass(hint) and issubclass(hint, tuple) and hasattr(hint, "_fields") def is_attrs(hint) -> bool: return attrs.has(hint) def is_annotated(hint) -> bool: return type(hint) is AnnotatedType def contains_hint(hint, target_type) -> bool: """Indicates if ``target_type`` is in a possibly annotated/unioned ``hint``. E.g. ``contains_hint(Union[int, str], str) == True`` """ hint = resolve(hint) if is_union(hint): return any(contains_hint(x, target_type) for x in get_args(hint)) else: return isclass(hint) and issubclass(hint, target_type) def is_typeddict(hint) -> bool: """Determine if a type annotation is a TypedDict. This is surprisingly hard! Modified from Beartype's implementation: https://github.com/beartype/beartype/blob/main/beartype/_util/hint/pep/proposal/utilpep589.py """ hint = resolve(hint) if is_union(hint): return any(is_typeddict(x) for x in get_args(hint)) if not (isclass(hint) and issubclass(hint, dict)): return False return ( # This "dict" subclass defines these "TypedDict" attributes *AND*... hasattr(hint, "__annotations__") and hasattr(hint, "__total__") and # Either... ( # The active Python interpreter targets exactly Python 3.8 and # thus fails to unconditionally define the remaining attributes # *OR*... _IS_PYTHON_3_8 or # The active Python interpreter targets any other Python version # and thus unconditionally defines the remaining attributes. (hasattr(hint, "__required_keys__") and hasattr(hint, "__optional_keys__")) ) ) def resolve(type_: Any) -> type: """Perform all simplifying resolutions.""" if type_ is inspect.Parameter.empty: return str type_prev = None while type_ != type_prev: type_prev = type_ type_ = resolve_annotated(type_) type_ = resolve_optional(type_) type_ = resolve_required(type_) type_ = resolve_new_type(type_) return type_ def resolve_optional(type_: Any) -> Any: """Only resolves Union's of None + one other type (i.e. Optional).""" # Python will automatically flatten out nested unions when possible. # So we don't need to loop over resolution. if not is_union(type_): return type_ non_none_types = [t for t in get_args(type_) if t is not NoneType] if not non_none_types: # pragma: no cover # This should never happen; python simplifies: # ``Union[None, None] -> NoneType`` raise ValueError("Union type cannot be all NoneType") elif len(non_none_types) == 1: type_ = non_none_types[0] elif len(non_none_types) > 1: return Union[tuple(resolve_optional(x) for x in non_none_types)] # pyright: ignore else: raise NotImplementedError return type_ def resolve_annotated(type_: Any) -> type: if type(type_) is AnnotatedType: type_ = get_args(type_)[0] return type_ def resolve_required(type_: Any) -> type: if get_origin(type_) in (Required, NotRequired): type_ = get_args(type_)[0] return type_ def resolve_new_type(type_: Any) -> type: try: return resolve_new_type(type_.__supertype__) except AttributeError: return type_ def get_hint_name(hint) -> str: if isinstance(hint, str): return hint if is_nonetype(hint): return "None" if hint is Any: return "Any" if is_union(hint): return "|".join(get_hint_name(arg) for arg in get_args(hint)) if origin := get_origin(hint): out = get_hint_name(origin) if args := get_args(hint): out += "[" + ", ".join(get_hint_name(arg) for arg in args) + "]" return out if hasattr(hint, "__name__"): return hint.__name__ if getattr(hint, "_name", None) is not None: return hint._name return str(hint) cyclopts-3.9.0/cyclopts/argument.py000066400000000000000000001407421475451620500174400ustar00rootroot00000000000000import inspect import itertools from contextlib import suppress from functools import partial from typing import Any, Callable, Optional, Sequence, Union, get_args, get_origin from attrs import define, field import cyclopts.utils from cyclopts._convert import ( convert, token_count, ) from cyclopts.annotations import ( contains_hint, is_attrs, is_dataclass, is_namedtuple, is_pydantic, is_typeddict, is_union, resolve, ) from cyclopts.exceptions import ( CoercionError, CycloptsError, MissingArgumentError, MixedArgumentError, RepeatArgumentError, ValidationError, ) from cyclopts.field_info import ( KEYWORD_ONLY, POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_KEYWORD, VAR_POSITIONAL, FieldInfo, _attrs_field_infos, _generic_class_field_infos, _pydantic_field_infos, _typed_dict_field_infos, get_field_infos, ) from cyclopts.group import Group from cyclopts.parameter import ITERATIVE_BOOL_IMPLICIT_VALUE, Parameter, get_parameters from cyclopts.token import Token from cyclopts.utils import UNSET, grouper, is_builtin # parameter subkeys should not inherit these parameter values from their parent. _PARAMETER_SUBKEY_BLOCKER = Parameter( name=None, converter=None, # pyright: ignore validator=None, negative=None, accepts_keys=None, consume_multiple=None, env_var=None, ) _SHOW_DEFAULT_BLOCKLIST = ( None, inspect.Parameter.empty, ) _kind_parent_child_reassignment = { (POSITIONAL_OR_KEYWORD, POSITIONAL_OR_KEYWORD): POSITIONAL_OR_KEYWORD, (POSITIONAL_OR_KEYWORD, POSITIONAL_ONLY): POSITIONAL_ONLY, (POSITIONAL_OR_KEYWORD, KEYWORD_ONLY): KEYWORD_ONLY, (POSITIONAL_OR_KEYWORD, VAR_POSITIONAL): VAR_POSITIONAL, (POSITIONAL_OR_KEYWORD, VAR_KEYWORD): VAR_KEYWORD, (POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD): POSITIONAL_ONLY, (POSITIONAL_ONLY, POSITIONAL_ONLY): POSITIONAL_ONLY, (POSITIONAL_ONLY, KEYWORD_ONLY): None, (POSITIONAL_ONLY, VAR_POSITIONAL): VAR_POSITIONAL, (POSITIONAL_ONLY, VAR_KEYWORD): None, (KEYWORD_ONLY, POSITIONAL_OR_KEYWORD): KEYWORD_ONLY, (KEYWORD_ONLY, POSITIONAL_ONLY): None, (KEYWORD_ONLY, KEYWORD_ONLY): KEYWORD_ONLY, (KEYWORD_ONLY, VAR_POSITIONAL): None, (KEYWORD_ONLY, VAR_KEYWORD): VAR_KEYWORD, (VAR_POSITIONAL, POSITIONAL_OR_KEYWORD): POSITIONAL_ONLY, (VAR_POSITIONAL, POSITIONAL_ONLY): POSITIONAL_ONLY, (VAR_POSITIONAL, KEYWORD_ONLY): None, (VAR_POSITIONAL, VAR_POSITIONAL): VAR_POSITIONAL, (VAR_POSITIONAL, VAR_KEYWORD): None, (VAR_KEYWORD, POSITIONAL_OR_KEYWORD): KEYWORD_ONLY, (VAR_KEYWORD, POSITIONAL_ONLY): None, (VAR_KEYWORD, KEYWORD_ONLY): KEYWORD_ONLY, (VAR_KEYWORD, VAR_POSITIONAL): None, (VAR_KEYWORD, VAR_KEYWORD): VAR_KEYWORD, } def _startswith(string, prefix): def normalize(s): return s.replace("_", "-") return normalize(string).startswith(normalize(prefix)) def _missing_keys_factory(get_field_info: Callable[[Any], dict[str, FieldInfo]]): def inner(argument: "Argument", data: dict) -> list[str]: provided_keys = set(data) field_info = get_field_info(argument.hint) return [k for k, v in field_info.items() if (v.required and k not in provided_keys)] return inner def _identity_converter(type_, token): return token class ArgumentCollection(list["Argument"]): """A list-like container for :class:`Argument`.""" def __init__(self, *args): super().__init__(*args) def copy(self) -> "ArgumentCollection": """Returns a shallow copy of the :class:`ArgumentCollection`.""" return type(self)(self) def match( self, term: Union[str, int], *, transform: Optional[Callable[[str], str]] = None, delimiter: str = ".", ) -> tuple["Argument", tuple[str, ...], Any]: """Matches CLI keyword or index to their :class:`Argument`. Parameters ---------- term: str | int One of: * :obj:`str` keyword like ``"--foo"`` or ``"-f"`` or ``"--foo.bar.baz"``. * :obj:`int` global positional index. Raises ------ ValueError If the provided ``term`` doesn't match. Returns ------- Argument Matched :class:`Argument`. Tuple[str, ...] Python keys into Argument. Non-empty iff Argument accepts keys. Any Implicit value (if a flag). :obj:`None` otherwise. """ best_match_argument, best_match_keys, best_implicit_value = None, None, None for argument in self: try: match_keys, implicit_value = argument.match(term, transform=transform, delimiter=delimiter) except ValueError: continue if best_match_keys is None or len(match_keys) < len(best_match_keys): best_match_keys = match_keys best_match_argument = argument best_implicit_value = implicit_value if not match_keys: # Perfect match break if best_match_argument is None or best_match_keys is None: raise ValueError(f"No Argument matches {term!r}") return best_match_argument, best_match_keys, best_implicit_value def _set_marks(self, val: bool): for argument in self: argument._marked = val def _convert(self): """Convert and validate all elements.""" self._set_marks(False) for argument in sorted(self, key=lambda x: x.keys): if argument._marked: continue argument.convert_and_validate() @classmethod def _from_type( cls, field_info: FieldInfo, keys: tuple[str, ...], *default_parameters, group_lookup: dict[str, Group], group_arguments: Group, group_parameters: Group, parse_docstring: bool = True, docstring_lookup: Optional[dict[tuple[str, ...], Parameter]] = None, positional_index: Optional[int] = None, _resolve_groups: bool = True, ): out = cls() # groups=list(group_lookup.values())) if docstring_lookup is None: docstring_lookup = {} cyclopts_parameters_no_group = [] hint = field_info.hint hint, hint_parameters = get_parameters(hint) cyclopts_parameters_no_group.extend(hint_parameters) if not keys: # root hint annotation if field_info.kind is field_info.VAR_KEYWORD: hint = dict[str, hint] elif field_info.kind is field_info.VAR_POSITIONAL: hint = tuple[hint, ...] if _resolve_groups: cyclopts_parameters = [] for cparam in cyclopts_parameters_no_group: resolved_groups = [] for group in cparam.group: # pyright:ignore if isinstance(group, str): group = group_lookup[group] resolved_groups.append(group) cyclopts_parameters.append(group.default_parameter) cyclopts_parameters.append(cparam) if resolved_groups: cyclopts_parameters.append(Parameter(group=resolved_groups)) else: cyclopts_parameters = cyclopts_parameters_no_group upstream_parameter = Parameter.combine( ( Parameter(group=group_arguments) if field_info.kind in (field_info.POSITIONAL_ONLY, field_info.VAR_POSITIONAL) else Parameter(group=group_parameters) ), *default_parameters, ) immediate_parameter = Parameter.combine(*cyclopts_parameters) # We do NOT want to skip parse=False arguments here. # This makes it easier to assemble ignored arguments downstream. # resolve/derive the parameter name if keys: cparam = Parameter.combine( upstream_parameter, _PARAMETER_SUBKEY_BLOCKER, immediate_parameter, ) cparam = Parameter.combine( cparam, Parameter( name=_resolve_parameter_name( upstream_parameter.name, # pyright: ignore immediate_parameter.name or tuple(cparam.name_transform(x) for x in field_info.names), # pyright: ignore ) ), ) else: # This is directly on iparam cparam = Parameter.combine( upstream_parameter, immediate_parameter, ) if cparam.name: if field_info.is_keyword: assert isinstance(cparam.name, tuple) cparam = Parameter.combine(cparam, Parameter(name=_resolve_parameter_name(cparam.name))) else: if field_info.kind in (field_info.POSITIONAL_ONLY, field_info.VAR_POSITIONAL): # Name is only used for help-string cparam = Parameter.combine(cparam, Parameter(name=(name.upper() for name in field_info.names))) elif field_info.kind is field_info.VAR_KEYWORD: cparam = Parameter.combine(cparam, Parameter(name=("--[KEYWORD]",))) else: # cparam.name_transform cannot be None due to: # attrs.converters.default_if_none(default_name_transform) assert cparam.name_transform is not None cparam = Parameter.combine( cparam, Parameter(name=("--" + cparam.name_transform(name) for name in field_info.names)) ) if field_info.is_keyword_only: positional_index = None argument = Argument(field_info=field_info, parameter=cparam, keys=keys, hint=hint) if not argument._accepts_keywords and positional_index is not None: argument.index = positional_index positional_index += 1 out.append(argument) if argument._accepts_keywords: hint_docstring_lookup = _extract_docstring_help(argument.hint) if parse_docstring else {} hint_docstring_lookup.update(docstring_lookup) for sub_field_name, sub_field_info in argument._lookup.items(): updated_kind = _kind_parent_child_reassignment[(argument.field_info.kind, sub_field_info.kind)] # pyright: ignore if updated_kind is None: continue sub_field_info.kind = updated_kind if sub_field_info.is_keyword_only: positional_index = None subkey_docstring_lookup = { k[1:]: v for k, v in hint_docstring_lookup.items() if k[0] == sub_field_name and len(k) > 1 } subkey_argument_collection = cls._from_type( sub_field_info, keys + (sub_field_name,), cparam, hint_docstring_lookup.get((sub_field_name,)), Parameter(required=argument.required & sub_field_info.required), group_lookup=group_lookup, group_arguments=group_arguments, group_parameters=group_parameters, parse_docstring=parse_docstring, docstring_lookup=subkey_docstring_lookup, positional_index=positional_index, _resolve_groups=_resolve_groups, ) if subkey_argument_collection: argument.children.append(subkey_argument_collection[0]) out.extend(subkey_argument_collection) if positional_index is not None: positional_index = subkey_argument_collection._max_index if positional_index is not None: positional_index += 1 return out @classmethod def _from_iparam( cls, iparam: inspect.Parameter, *default_parameters: Optional[Parameter], group_lookup: Optional[dict[str, Group]] = None, group_arguments: Optional[Group] = None, group_parameters: Optional[Group] = None, positional_index: Optional[int] = None, parse_docstring: bool = True, docstring_lookup: Optional[dict[tuple[str, ...], Parameter]] = None, _resolve_groups: bool = True, ): # The responsibility of this function is to extract out the root type # and annotation. The rest of the functionality goes into _from_type. if group_lookup is None: group_lookup = {} if group_arguments is None: group_arguments = Group.create_default_arguments() if group_parameters is None: group_parameters = Group.create_default_parameters() group_lookup[group_arguments.name] = group_arguments group_lookup[group_parameters.name] = group_parameters return cls._from_type( FieldInfo.from_iparam(iparam), (), *default_parameters, group_lookup=group_lookup, group_arguments=group_arguments, group_parameters=group_parameters, positional_index=positional_index, docstring_lookup=docstring_lookup, parse_docstring=parse_docstring, _resolve_groups=_resolve_groups, ) @classmethod def _from_callable( cls, func: Callable, *default_parameters: Optional[Parameter], group_lookup: Optional[dict[str, Group]] = None, group_arguments: Optional[Group] = None, group_parameters: Optional[Group] = None, parse_docstring: bool = True, _resolve_groups: bool = True, ): import cyclopts.utils out = cls() if group_arguments is None: group_arguments = Group.create_default_arguments() if group_parameters is None: group_parameters = Group.create_default_parameters() if _resolve_groups: group_lookup = { group.name: group for group in _resolve_groups_from_callable( func, *default_parameters, group_arguments=group_arguments, group_parameters=group_parameters, ) } docstring_lookup = _extract_docstring_help(func) if parse_docstring else {} positional_index = 0 for iparam in cyclopts.utils.signature(func).parameters.values(): if parse_docstring: subkey_docstring_lookup = { k[1:]: v for k, v in docstring_lookup.items() if k[0] == iparam.name and len(k) > 1 } else: subkey_docstring_lookup = None iparam_argument_collection = cls._from_iparam( iparam, *default_parameters, docstring_lookup.get((iparam.name,)), group_lookup=group_lookup, group_arguments=group_arguments, group_parameters=group_parameters, positional_index=positional_index, parse_docstring=parse_docstring, docstring_lookup=subkey_docstring_lookup, _resolve_groups=_resolve_groups, ) if positional_index is not None: positional_index = iparam_argument_collection._max_index if positional_index is not None: positional_index += 1 out.extend(iparam_argument_collection) return out @property def groups(self): groups = [] for argument in self: assert isinstance(argument.parameter.group, tuple) for group in argument.parameter.group: if group not in groups: groups.append(group) return groups @property def _root_arguments(self): for argument in self: if not argument.keys: yield argument @property def _max_index(self) -> Optional[int]: return max((x.index for x in self if x.index is not None), default=None) def filter_by( self, *, group: Optional[Group] = None, has_tokens: Optional[bool] = None, has_tree_tokens: Optional[bool] = None, keys_prefix: Optional[tuple[str, ...]] = None, kind: Optional[inspect._ParameterKind] = None, parse: Optional[bool] = None, show: Optional[bool] = None, value_set: Optional[bool] = None, ) -> "ArgumentCollection": """Filter the :class:`ArgumentCollection`. All non-:obj:`None` filters will be applied. Parameters ---------- group: Optional[Group] The :class:`.Group` the arguments should be in. has_tokens: Optional[bool] Immediately has tokens (not including children). has_tree_tokens: Optional[bool] Argument and/or it's children have parsed tokens. kind: Optional[inspect._ParameterKind] The :attr:`~inspect.Parameter.kind` of the argument. parse: Optional[bool] If the argument is intended to be parsed or not. show: Optional[bool] The Argument is intended to be show on the help page. value_set: Optional[bool] The converted value is set. """ ac = self.copy() cls = type(self) if group is not None: ac = cls(x for x in ac if group in x.parameter.group) # pyright: ignore if kind is not None: ac = cls(x for x in ac if x.field_info.kind == kind) if has_tokens is not None: ac = cls(x for x in ac if not (bool(x.tokens) ^ bool(has_tokens))) if has_tree_tokens is not None: ac = cls(x for x in ac if not (bool(x.tokens) ^ bool(has_tree_tokens))) if keys_prefix is not None: ac = cls(x for x in ac if x.keys[: len(keys_prefix)] == keys_prefix) if show is not None: ac = cls(x for x in ac if not (x.show ^ bool(show))) if value_set is not None: ac = cls(x for x in ac if ((x.value is UNSET) ^ bool(value_set))) if parse is not None: ac = cls(x for x in ac if not (x.parameter.parse ^ parse)) return ac @define(kw_only=True) class Argument: """Encapsulates functionality and additional contextual information for parsing a parameter. An argument is defined as anything that would have its own entry in the help page. """ tokens: list[Token] = field(factory=list) """ List of :class:`.Token` parsed from various sources. Do not directly mutate; see :meth:`append`. """ field_info: FieldInfo = field(default=None) """ Additional information about the parameter from surrounding python syntax. """ parameter: Parameter = field(factory=Parameter) # pyright: ignore """ Fully resolved user-provided :class:`.Parameter`. """ hint: Any = field(default=str, converter=resolve) """ The type hint for this argument; may be different from :attr:`.FieldInfo.annotation`. """ index: Optional[int] = field(default=None) """ Associated python positional index for argument. If ``None``, then cannot be assigned positionally. """ keys: tuple[str, ...] = field(default=()) """ **Python** keys that lead to this leaf. ``self.parameter.name`` and ``self.keys`` can naively disagree! For example, a ``self.parameter.name="--foo.bar.baz"`` could be aliased to "--fizz". The resulting ``self.keys`` would be ``("bar", "baz")``. This is populated based on type-hints and class-structure, not ``Parameter.name``. .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass from typing import Annotated app = App() @dataclass class User: id: int name: Annotated[str, Parameter(name="--fullname")] @app.default def main(user: User): pass for argument in app.assemble_argument_collection(): print(f"name: {argument.name:16} hint: {str(argument.hint):16} keys: {str(argument.keys)}") .. code-block:: bash $ my-script name: --user.id hint: keys: ('id',) name: --fullname hint: keys: ('name',) """ # Converted value; may be stale. _value: Any = field(alias="value", default=UNSET) """ Converted value from last :meth:`convert` call. This value may be stale if fields have changed since last :meth:`convert` call. :class:`.UNSET` if :meth:`convert` has not yet been called with tokens. """ _accepts_keywords: bool = field(default=False, init=False, repr=False) _default: Any = field(default=None, init=False, repr=False) _lookup: dict[str, FieldInfo] = field(factory=dict, init=False, repr=False) children: "ArgumentCollection" = field(factory=ArgumentCollection, init=False, repr=False) """ Collection of other :class:`Argument` that eventually culminate into the python variable represented by :attr:`field_info`. """ _marked_converted: bool = field(default=False, init=False, repr=False) # for mark & sweep algos _mark_converted_override: bool = field(default=False, init=False, repr=False) # Validator to be called based on builtin type support. _missing_keys_checker: Optional[Callable] = field(default=None, init=False, repr=False) _internal_converter: Optional[Callable] = field(default=None, init=False, repr=False) def __attrs_post_init__(self): # By definition, self.hint is Not AnnotatedType hint = resolve(self.hint) hints = get_args(hint) if is_union(hint) else (hint,) if not self.parameter.parse: return if self.parameter.accepts_keys is False: # ``None`` means to infer. return for hint in hints: # ``self.parameter.accepts_keys`` is either ``None`` or ``True`` here origin = get_origin(hint) hint_origin = {hint, origin} # Classes that ALWAYS takes keywords (accepts_keys=None) field_infos = get_field_infos(hint) if dict in hint_origin: self._accepts_keywords = True key_type, val_type = str, str args = get_args(hint) with suppress(IndexError): key_type = args[0] val_type = args[1] if key_type is not str: raise TypeError('Dictionary type annotations must have "str" keys.') self._default = val_type elif is_typeddict(hint): self._missing_keys_checker = _missing_keys_factory(_typed_dict_field_infos) self._accepts_keywords = True self._lookup.update(field_infos) elif is_dataclass(hint): # Typical usecase of a dataclass will have more than 1 field. self._missing_keys_checker = _missing_keys_factory(_generic_class_field_infos) self._accepts_keywords = True self._lookup.update(field_infos) elif is_namedtuple(hint): # collections.namedtuple does not have type hints, assume "str" for everything. self._missing_keys_checker = _missing_keys_factory(_generic_class_field_infos) self._accepts_keywords = True if not hasattr(hint, "__annotations__"): raise ValueError("Cyclopts cannot handle collections.namedtuple in python <3.10.") self._lookup.update(field_infos) elif is_attrs(hint): self._missing_keys_checker = _missing_keys_factory(_attrs_field_infos) self._accepts_keywords = True self._lookup.update(field_infos) elif is_pydantic(hint): self._missing_keys_checker = _missing_keys_factory(_pydantic_field_infos) self._accepts_keywords = True # pydantic's __init__ signature doesn't accurately reflect its requirements. # so we cannot use _generic_class_required_optional(...) self._lookup.update(field_infos) elif not is_builtin(hint) and field_infos: # Some classic user class. self._missing_keys_checker = _missing_keys_factory(_generic_class_field_infos) self._accepts_keywords = True self._lookup.update(field_infos) elif self.parameter.accepts_keys is None: # Typical builtin hint continue if self.parameter.accepts_keys is None: continue # Only explicit ``self.parameter.accepts_keys == True`` from here on # Classes that MAY take keywords (accepts_keys=True) # They must be explicitly specified ``accepts_keys=True`` because otherwise # providing a single positional argument is what we want. self._accepts_keywords = True self._missing_keys_checker = _missing_keys_factory(_generic_class_field_infos) for i, iparam in enumerate(cyclopts.utils.signature(hint.__init__).parameters.values()): if i == 0 and iparam.name == "self": continue if iparam.kind is iparam.VAR_KEYWORD: self._default = iparam.annotation else: self._lookup[iparam.name] = FieldInfo.from_iparam(iparam) @property def value(self): """Converted value from last :meth:`convert` call. This value may be stale if fields have changed since last :meth:`convert` call. :class:`.UNSET` if :meth:`convert` has not yet been called with tokens. """ return self._value @value.setter def value(self, val): if self._marked: self._mark_converted_override = True self._marked = True self._value = val @property def _marked(self): """If ``True``, then this node in the tree has already been converted and ``value`` has been populated.""" return self._marked_converted | self._mark_converted_override @_marked.setter def _marked(self, value: bool): self._marked_converted = value @property def _accepts_arbitrary_keywords(self) -> bool: args = get_args(self.hint) if is_union(self.hint) else (self.hint,) return any(dict in (arg, get_origin(arg)) for arg in args) @property def show_default(self) -> bool: """Show the default value on the help page.""" if self.parameter.show_default is None: return not self.required and self.field_info.default not in _SHOW_DEFAULT_BLOCKLIST else: return self.parameter.show_default def _type_hint_for_key(self, key: str): try: return self._lookup[key].annotation except KeyError: if self._default is None: raise return self._default def _should_attempt_json_dict(self, tokens: Optional[Sequence[Union[Token, str]]] = None) -> bool: """When parsing, should attempt to parse the token(s) as json dict data.""" if tokens is None: tokens = self.tokens if not tokens: return False if not self._accepts_keywords: return False value = tokens[0].value if isinstance(tokens[0], Token) else tokens[0] if not value.strip().startswith("{"): return False if self.parameter.json_dict is not None: return self.parameter.json_dict if contains_hint(self.field_info.annotation, str): return False return True def _should_attempt_json_list( self, tokens: Union[Sequence[Union[Token, str]], Token, str, None] = None, keys: tuple[str, ...] = () ) -> bool: """When parsing, should attempt to parse the token(s) as json list data.""" if tokens is None: tokens = self.tokens if not tokens: return False _, consume_all = self.token_count(keys) if not consume_all: return False if isinstance(tokens, Token): value = tokens.value elif isinstance(tokens, str): value = tokens else: value = tokens[0].value if isinstance(tokens[0], Token) else tokens[0] if not value.strip().startswith("["): return False if self.parameter.json_list is not None: return self.parameter.json_list for arg in get_args(self.field_info.annotation) or (str,): if contains_hint(arg, str): return False return True def match( self, term: Union[str, int], *, transform: Optional[Callable[[str], str]] = None, delimiter: str = ".", ) -> tuple[tuple[str, ...], Any]: """Match a name search-term, or a positional integer index. Raises ------ ValueError If no match is found. Returns ------- Tuple[str, ...] Leftover keys after matching to this argument. Used if this argument accepts_arbitrary_keywords. Any Implicit value. :obj:`None` if no implicit value is applicable. """ if not self.parameter.parse: raise ValueError return ( self._match_index(term) if isinstance(term, int) else self._match_name(term, transform=transform, delimiter=delimiter) ) def _match_name( self, term: str, *, transform: Optional[Callable[[str], str]] = None, delimiter: str = ".", ) -> tuple[tuple[str, ...], Any]: """Check how well this argument matches a token keyword identifier. Parameters ---------- term: str Something like "--foo" transform: Callable Function that converts the cyclopts Parameter name(s) into something that should be compared against ``term``. Raises ------ ValueError If no match found. Returns ------- Tuple[str, ...] Leftover keys after matching to this argument. Used if this argument accepts_arbitrary_keywords. Any Implicit value. """ if self.field_info.kind is self.field_info.VAR_KEYWORD: return tuple(term.lstrip("-").split(delimiter)), None assert self.parameter.name for name in self.parameter.name: if transform: name = transform(name) if _startswith(term, name): trailing = term[len(name) :] implicit_value = True if self.hint is bool or self.hint in ITERATIVE_BOOL_IMPLICIT_VALUE else None if trailing: if trailing[0] == delimiter: trailing = trailing[1:] break # Otherwise, it's not an actual match. else: # exact match return (), implicit_value else: # No positive-name matches found. for name in self.negatives: if transform: name = transform(name) if term.startswith(name): trailing = term[len(name) :] if self.hint in ITERATIVE_BOOL_IMPLICIT_VALUE: implicit_value = False else: implicit_value = (get_origin(self.hint) or self.hint)() if trailing: if trailing[0] == delimiter: trailing = trailing[1:] break # Otherwise, it's not an actual match. else: # exact match return (), implicit_value else: # No negative-name matches found. raise ValueError if not self._accepts_arbitrary_keywords: # Still not an actual match. raise ValueError return tuple(trailing.split(delimiter)), implicit_value def _match_index(self, index: int) -> tuple[tuple[str, ...], Any]: if self.index is None: raise ValueError elif self.field_info.kind is self.field_info.VAR_POSITIONAL: if index < self.index: raise ValueError elif index != self.index: raise ValueError return (), None def append(self, token: Token): """Safely add a :class:`Token`.""" if not self.parameter.parse: raise ValueError if any(x.address == token.address for x in self.tokens): _, consume_all = self.token_count(token.keys) if not consume_all: raise RepeatArgumentError(token=token) if self.tokens: if bool(token.keys) ^ any(x.keys for x in self.tokens): raise MixedArgumentError(argument=self) self.tokens.append(token) @property def has_tokens(self) -> bool: """This argument, or a child argument, has at least 1 parsed token.""" # noqa: D404 return bool(self.tokens) or any(x.has_tokens for x in self.children) @property def children_recursive(self) -> "ArgumentCollection": out = ArgumentCollection() for child in self.children: out.append(child) out.extend(child.children_recursive) return out def _convert(self, converter: Optional[Callable] = None): if converter is None: converter = self.parameter.converter def safe_converter(hint, tokens): if isinstance(tokens, dict): try: return converter(hint, tokens) # pyright: ignore except (AssertionError, ValueError, TypeError) as e: raise CoercionError(msg=e.args[0] if e.args else None, argument=self, target_type=hint) from e else: try: return converter(hint, tokens) # pyright: ignore except (AssertionError, ValueError, TypeError) as e: token = tokens[0] if len(tokens) == 1 else None raise CoercionError( msg=e.args[0] if e.args else None, argument=self, target_type=hint, token=token ) from e if not self.parameter.parse: out = UNSET elif not self.children: positional: list[Token] = [] keyword = {} def expand_tokens(tokens): for token in tokens: if self._should_attempt_json_list(token): import json try: parsed_json = json.loads(token.value) except json.JSONDecodeError as e: raise CoercionError(token=token, target_type=self.hint) from e if not isinstance(parsed_json, list): raise CoercionError(token=token, target_type=self.hint) for element in parsed_json: if element is None: yield token.evolve(value="", implicit_value=element) else: yield token.evolve(value=str(element)) else: yield token expanded_tokens = list(expand_tokens(self.tokens)) for token in expanded_tokens: if token.implicit_value is not UNSET and isinstance( token.implicit_value, get_origin(self.hint) or self.hint ): assert len(expanded_tokens) == 1 return token.implicit_value if token.keys: lookup = keyword for key in token.keys[:-1]: lookup = lookup.setdefault(key, {}) lookup.setdefault(token.keys[-1], []).append(token) else: positional.append(token) if positional and keyword: # pragma: no cover # This should never happen due to checks in ``Argument.append`` raise MixedArgumentError(argument=self) if positional: if self.field_info and self.field_info.kind is self.field_info.VAR_POSITIONAL: # Apply converter to individual values hint = get_args(self.hint)[0] tokens_per_element, _ = self.token_count() out = tuple(safe_converter(hint, values) for values in grouper(positional, tokens_per_element)) else: out = safe_converter(self.hint, tuple(positional)) elif keyword: if self.field_info and self.field_info.kind is self.field_info.VAR_KEYWORD and not self.keys: # Apply converter to individual values out = {key: safe_converter(get_args(self.hint)[1], value) for key, value in keyword.items()} else: out = safe_converter(self.hint, keyword) elif self.required: raise MissingArgumentError(argument=self) else: # no tokens return UNSET else: # A dictionary-like structure. data = {} if is_pydantic(self.hint): # Don't convert any subkeys, let pydantic handle them. converter = partial( convert, converter=_identity_converter, name_transform=self.parameter.name_transform ) if self._should_attempt_json_dict(): # Dict-like structures may have incoming json data from an environment variable. # Pass these values along as Tokens to children. import json from cyclopts.config._common import update_argument_collection for token in self.tokens: try: parsed_json = json.loads(token.value) except json.JSONDecodeError as e: raise CoercionError(token=token, target_type=self.hint) from e update_argument_collection( {self.name.lstrip("-"): parsed_json}, token.source, self.children_recursive, root_keys=(), allow_unknown=False, ) for child in self.children: assert len(child.keys) == (len(self.keys) + 1) if child.has_tokens: # Either the child directly has tokens, or a nested child has tokens. data[child.keys[-1]] = child.convert_and_validate(converter=converter) elif child.required: # Check if the required fields are already populated. obj = data for k in child.keys: try: obj = obj[k] except Exception: raise MissingArgumentError(argument=child) from None child._marked = True if self._missing_keys_checker and (self.required or data): if missing_keys := self._missing_keys_checker(self, data): # Report the first missing argument. missing_key = missing_keys[0] keys = self.keys + (missing_key,) missing_arguments = self.children.filter_by(keys_prefix=keys) if missing_arguments: raise MissingArgumentError(argument=missing_arguments[0]) else: missing_description = self.field_info.names[0] + "->" + "->".join(keys) raise ValueError( f'Required field "{missing_description}" is not accessible by Cyclopts; possibly due to conflicting POSITIONAL/KEYWORD requirements.' ) if data: out = self.hint(**data) elif self.required: # This should NEVER happen: empty data to a required dict field. raise MissingArgumentError(argument=self) # pragma: no cover else: out = UNSET return out def convert(self, converter: Optional[Callable] = None): """Converts :attr:`tokens` into :attr:`value`. Parameters ---------- converter: Optional[Callable] Converter function to use. Overrides ``self.parameter.converter`` Returns ------- Any The converted data. Same as :attr:`value`. """ if not self._marked: try: self.value = self._convert(converter=converter) except CoercionError as e: if e.argument is None: e.argument = self if e.target_type is None: e.target_type = self.hint raise except CycloptsError as e: if e.argument is None: e.argument = self raise return self.value def validate(self, value): """Validates provided value. Parameters ---------- value: Value to validate. Returns ------- Any The converted data. Same as :attr:`value`. """ assert isinstance(self.parameter.validator, tuple) try: if not self.keys and self.field_info and self.field_info.kind is self.field_info.VAR_KEYWORD: hint = get_args(self.hint)[1] for validator in self.parameter.validator: for val in value.values(): validator(hint, val) elif self.field_info and self.field_info.kind is self.field_info.VAR_POSITIONAL: hint = get_args(self.hint)[0] for validator in self.parameter.validator: for val in value: validator(hint, val) else: for validator in self.parameter.validator: validator(self.hint, value) except (AssertionError, ValueError, TypeError) as e: raise ValidationError(exception_message=e.args[0] if e.args else "", argument=self) from e def convert_and_validate(self, converter: Optional[Callable] = None): """Converts and validates :attr:`tokens` into :attr:`value`. Parameters ---------- converter: Optional[Callable] Converter function to use. Overrides ``self.parameter.converter`` Returns ------- Any The converted data. Same as :attr:`value`. """ val = self.convert(converter=converter) if val is not UNSET: self.validate(val) return val def token_count(self, keys: tuple[str, ...] = ()): """The number of string tokens this argument consumes. Parameters ---------- keys: tuple[str, ...] The **python** keys into this argument. If provided, returns the number of string tokens that specific data type within the argument consumes. Returns ------- int Number of string tokens to create 1 element. consume_all: bool :obj:`True` if this data type is iterable. """ if len(keys) > 1: hint = self._default elif len(keys) == 1: hint = self._type_hint_for_key(keys[0]) else: hint = self.hint tokens_per_element, consume_all = token_count(hint) return tokens_per_element, consume_all @property def negatives(self): """Negative flags from :meth:`.Parameter.get_negatives`.""" return self.parameter.get_negatives(self.hint) @property def name(self) -> str: """The **first** provided name this argument goes by.""" return self.names[0] @property def names(self) -> tuple[str, ...]: """Names the argument goes by (both positive and negative).""" assert isinstance(self.parameter.name, tuple) return tuple(itertools.chain(self.parameter.name, self.negatives)) def env_var_split(self, value: str, delimiter: Optional[str] = None) -> list[str]: """Split a given value with :meth:`.Parameter.env_var_split`.""" return self.parameter.env_var_split(self.hint, value, delimiter=delimiter) @property def show(self) -> bool: """Show this argument on the help page. If an argument has child arguments, don't show it on the help-page. """ return not self.children and self.parameter.show @property def required(self) -> bool: """Whether or not this argument requires a user-provided value.""" if self.parameter.required is None: return self.field_info.required else: return self.parameter.required def _resolve_groups_from_callable( func: Callable, *default_parameters: Optional[Parameter], group_arguments: Optional[Group] = None, group_parameters: Optional[Group] = None, ) -> list[Group]: argument_collection = ArgumentCollection._from_callable( func, *default_parameters, group_arguments=group_arguments, group_parameters=group_parameters, parse_docstring=False, _resolve_groups=False, ) resolved_groups = [] if group_arguments is not None: resolved_groups.append(group_arguments) if group_parameters is not None: resolved_groups.append(group_parameters) for argument in argument_collection: for group in argument.parameter.group: # pyright: ignore if not isinstance(group, Group): continue # Ensure a different, but same-named group doesn't already exist if any(group != x and x.name == group.name for x in resolved_groups): raise ValueError("Cannot register 2 distinct Group objects with same name.") if group.default_parameter is not None and group.default_parameter.group: # This shouldn't be possible due to ``Group`` internal checks. raise ValueError("Group.default_parameter cannot have a specified group.") # pragma: no cover try: next(x for x in resolved_groups if x.name == group.name) except StopIteration: resolved_groups.append(group) for argument in argument_collection: for group in argument.parameter.group: # pyright: ignore if not isinstance(group, str): continue try: next(x for x in resolved_groups if x.name == group) except StopIteration: resolved_groups.append(Group(group)) return resolved_groups def _extract_docstring_help(f: Callable) -> dict[tuple[str, ...], Parameter]: from docstring_parser import parse_from_object try: return { tuple(dparam.arg_name.split(".")): Parameter(help=dparam.description) for dparam in parse_from_object(f).params } except TypeError: # Type hints like ``dict[str, str]`` trigger this. return {} def _resolve_parameter_name_helper(elem): if elem.endswith("*"): elem = elem[:-1].rstrip(".") if elem and not elem.startswith("-"): elem = "--" + elem return elem def _resolve_parameter_name(*argss: tuple[str, ...]) -> tuple[str, ...]: """Resolve parameter names by combining and formatting multiple tuples of strings. Parameters ---------- *argss Each tuple represents a group of parameter name components. Returns ------- tuple[str, ...] A tuple of resolved parameter names. """ argss = tuple(x for x in argss if x) if len(argss) == 0: return () elif len(argss) == 1: return tuple("*" if x == "*" else _resolve_parameter_name_helper(x) for x in argss[0]) # Combine the first 2, and do a recursive call. out = [] for a1 in argss[0]: a1 = _resolve_parameter_name_helper(a1) for a2 in argss[1]: if a2.startswith("-") or not a1: out.append(a2) else: out.append(a1 + "." + a2) return _resolve_parameter_name(tuple(out), *argss[2:]) cyclopts-3.9.0/cyclopts/bind.py000066400000000000000000000354471475451620500165370ustar00rootroot00000000000000import inspect import itertools import os import shlex import sys from collections.abc import Iterable from contextlib import suppress from functools import partial from typing import TYPE_CHECKING, Callable, Sequence, Union import cyclopts.utils from cyclopts._convert import _bool from cyclopts.argument import ArgumentCollection from cyclopts.exceptions import ( ArgumentOrderError, CoercionError, CycloptsError, MissingArgumentError, UnknownOptionError, ValidationError, ) from cyclopts.field_info import POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD from cyclopts.token import Token from cyclopts.utils import UNSET, is_option_like if TYPE_CHECKING: from cyclopts.group import Group CliToken = partial(Token, source="cli") def normalize_tokens(tokens: Union[None, str, Iterable[str]]) -> list[str]: if tokens is None: tokens = sys.argv[1:] # Remove the executable elif isinstance(tokens, str): tokens = shlex.split(tokens) else: tokens = list(tokens) return tokens def _common_root_keys(argument_collection) -> tuple[str, ...]: if not argument_collection: return () common = argument_collection[0].keys for argument in argument_collection[1:]: if not argument.keys: return () for i, (common_key, argument_key) in enumerate(zip(common, argument.keys)): if common_key != argument_key: if i == 0: return () common = argument.keys[:i] break common = common[: len(argument.keys)] return common def _parse_kw_and_flags( argument_collection: ArgumentCollection, tokens: Sequence[str], *, end_of_options_delimiter: str = "--", ): unused_tokens, positional_only_tokens = [], [] skip_next_iterations = 0 if end_of_options_delimiter: try: delimiter_index = tokens.index(end_of_options_delimiter) except ValueError: pass # end_of_options_delimiter not in token stream else: positional_only_tokens = tokens[delimiter_index:] tokens = tokens[:delimiter_index] for i, token in enumerate(tokens): # If the previous argument was a keyword, then this is its value if skip_next_iterations > 0: skip_next_iterations -= 1 continue if not is_option_like(token): unused_tokens.append(token) continue cli_values: list[str] = [] consume_count = 0 if "=" in token: cli_option, cli_value = token.split("=", 1) cli_values.append(cli_value) consume_count -= 1 else: cli_option = token try: argument, leftover_keys, implicit_value = argument_collection.match(cli_option) except ValueError: unused_tokens.append(token) continue if implicit_value is not None: # A flag was parsed if cli_values: try: coerced_value = _bool(cli_values[-1]) except CoercionError as e: if e.token is None: e.token = CliToken(keyword=cli_option) if e.argument is None: e.argument = argument raise if coerced_value: # --positive-flag=true or --negative-flag=true or --empty-flag=true argument.append(CliToken(keyword=cli_option, implicit_value=implicit_value)) else: # --positive-flag=false or --negative-flag=false or --empty-flag=false if isinstance(implicit_value, bool): argument.append(CliToken(keyword=cli_option, implicit_value=not implicit_value)) else: continue else: argument.append(CliToken(keyword=cli_option, implicit_value=implicit_value)) else: tokens_per_element, consume_all = argument.token_count(leftover_keys) # Consume the appropriate number of tokens with suppress(IndexError): if consume_all and argument.parameter.consume_multiple: for j in itertools.count(): token = tokens[i + 1 + j] if not argument.parameter.allow_leading_hyphen and is_option_like(token): break cli_values.append(token) skip_next_iterations += 1 else: consume_count += tokens_per_element for j in range(consume_count): if len(cli_values) == 1 and ( argument._should_attempt_json_dict(cli_values) or argument._should_attempt_json_list(cli_values, leftover_keys) ): tokens_per_element = 1 # Assume that the contents are json and that we shouldn't # consume any additional tokens. break token = tokens[i + 1 + j] if not argument.parameter.allow_leading_hyphen and is_option_like(token): raise MissingArgumentError( argument=argument, tokens_so_far=cli_values, ) cli_values.append(token) skip_next_iterations += 1 if not cli_values or len(cli_values) % tokens_per_element: raise MissingArgumentError(argument=argument, tokens_so_far=cli_values) for index, cli_value in enumerate(cli_values): argument.append(CliToken(keyword=cli_option, value=cli_value, index=index, keys=leftover_keys)) unused_tokens.extend(positional_only_tokens) return unused_tokens def _future_positional_only_token_count(argument_collection: ArgumentCollection, starting_index: int) -> int: n_tokens_to_leave = 0 for i in itertools.count(): try: argument, _, _ = argument_collection.match(starting_index + i) except ValueError: break if argument.field_info.kind is not POSITIONAL_ONLY: break future_tokens_per_element, future_consume_all = argument.token_count() if future_consume_all: raise ValueError("Cannot have 2 all-consuming positional arguments.") n_tokens_to_leave += future_tokens_per_element return n_tokens_to_leave def _preprocess_positional_tokens(tokens: Sequence[str], end_of_options_delimiter: str) -> list[tuple[str, bool]]: try: delimiter_index = tokens.index(end_of_options_delimiter) return [(t, False) for t in tokens[:delimiter_index]] + [(t, True) for t in tokens[delimiter_index + 1 :]] except ValueError: # delimiter not found return [(t, False) for t in tokens] def _parse_pos( argument_collection: ArgumentCollection, tokens: list[str], *, end_of_options_delimiter: str = "--", ) -> list[str]: prior_positional_or_keyword_supplied_as_keyword_arguments = [] if not tokens: return [] tokens_and_force_positional = _preprocess_positional_tokens(tokens, end_of_options_delimiter) for i in itertools.count(): try: argument, _, _ = argument_collection.match(i) except ValueError: break if argument.field_info.kind is POSITIONAL_OR_KEYWORD: if argument.tokens and argument.tokens[0].keyword is not None: prior_positional_or_keyword_supplied_as_keyword_arguments.append(argument) # Continue in case we hit a VAR_POSITIONAL argument. continue if prior_positional_or_keyword_supplied_as_keyword_arguments: token = tokens[0] if not argument.parameter.allow_leading_hyphen and is_option_like(token): # It's more meaningful to interpret the token as an intended option, # rather than an intended positional value for ``argument``. raise UnknownOptionError(token=CliToken(value=token), argument_collection=argument_collection) else: raise ArgumentOrderError( argument=argument, prior_positional_or_keyword_supplied_as_keyword_arguments=prior_positional_or_keyword_supplied_as_keyword_arguments, token=tokens_and_force_positional[0][0], ) tokens_per_element, consume_all = argument.token_count() tokens_per_element = max(1, tokens_per_element) if consume_all and argument.field_info.kind is POSITIONAL_ONLY: # POSITIONAL_ONLY parameters can come after a POSITIONAL_ONLY list/iterable. # This makes it easier to create programs that do something like: # $ python my-program.py input_folder/*.csv output.csv # Need to see how many tokens we need to leave for subsequent POSITIONAL_ONLY parameters. n_tokens_to_leave = _future_positional_only_token_count(argument_collection, i + 1) else: n_tokens_to_leave = 0 new_tokens = [] while (len(tokens_and_force_positional) - n_tokens_to_leave) > 0: if (len(tokens_and_force_positional) - n_tokens_to_leave) < tokens_per_element: raise MissingArgumentError( argument=argument, tokens_so_far=[x[0] for x in tokens_and_force_positional], ) for index, (token, force_positional) in enumerate(tokens_and_force_positional[:tokens_per_element]): if not force_positional and not argument.parameter.allow_leading_hyphen and is_option_like(token): raise UnknownOptionError(token=CliToken(value=token), argument_collection=argument_collection) new_tokens.append(CliToken(value=token, index=index)) tokens_and_force_positional = tokens_and_force_positional[tokens_per_element:] if not consume_all: break argument.tokens[:0] = new_tokens # Prepend the new tokens to the argument. if not tokens_and_force_positional: break return [x[0] for x in tokens_and_force_positional] def _parse_env(argument_collection: ArgumentCollection): for argument in argument_collection: if argument.tokens: # Don't check environment variables for parameters that already have values from CLI. continue assert argument.parameter.env_var is not None for env_var_name in argument.parameter.env_var: try: env_var_value = os.environ[env_var_name] except KeyError: pass else: argument.tokens.append(Token(keyword=env_var_name, value=env_var_value, source="env")) break def _bind( argument_collection: ArgumentCollection, func: Callable, ): """Bind the mapping to the function signature.""" signature = cyclopts.utils.signature(func) bound = signature.bind_partial() for argument in argument_collection._root_arguments: if argument.value is not UNSET: bound.arguments[argument.field_info.name] = argument.value return bound def _parse_configs(argument_collection: ArgumentCollection, configs): for config in configs: # Each ``config`` is a partial that already has apps and commands provided. config(argument_collection) def _sort_group(argument_collection) -> list[tuple["Group", ArgumentCollection]]: """Sort groups into "deepest common-root-keys first" order. This is imperfect, but probably works sufficiently well for practical use-cases. """ out = {} # Sort alphabetically by group-name to enfroce some determinism. for i, group in enumerate(sorted(argument_collection.groups, key=lambda x: x.name)): if not (group_arguments := argument_collection.filter_by(group=group, has_tree_tokens=True)): continue common_root_keys = _common_root_keys(group_arguments) # Add i to key so that we don't get collisions. out[(common_root_keys, i)] = (group, group_arguments.filter_by(keys_prefix=common_root_keys)) return [ga for _, ga in sorted(out.items(), reverse=True)] def create_bound_arguments( func: Callable, argument_collection: ArgumentCollection, tokens: list[str], configs: Iterable[Callable], *, end_of_options_delimiter: str = "--", ) -> tuple[inspect.BoundArguments, list[str]]: """Parse and coerce CLI tokens to match a function's signature. Parameters ---------- func: Callable Function. argument_collection: ArgumentCollection tokens: List[str] CLI tokens to parse and coerce to match ``f``'s signature. configs: Iterable[Callable] end_of_options_delimiter: str Everything after this special token is forced to be supplied as a positional argument. Returns ------- bound: inspect.BoundArguments The converted and bound positional and keyword arguments for ``f``. unused_tokens: List[str] Remaining tokens that couldn't be matched to ``f``'s signature. """ unused_tokens = tokens try: unused_tokens = _parse_kw_and_flags( argument_collection, unused_tokens, end_of_options_delimiter=end_of_options_delimiter ) unused_tokens = _parse_pos( argument_collection, unused_tokens, end_of_options_delimiter=end_of_options_delimiter ) _parse_env(argument_collection) _parse_configs(argument_collection, configs) argument_collection._convert() groups_with_arguments = _sort_group(argument_collection) try: for group, group_arguments in groups_with_arguments: for validator in group.validator: # pyright: ignore validator(group_arguments) # pyright: ignore[reportOptionalCall] except (AssertionError, ValueError, TypeError) as e: raise ValidationError(exception_message=e.args[0] if e.args else "", group=group) from e # pyright: ignore for argument in argument_collection: # if a dict-like argument is missing, raise a MissingArgumentError on the first # required child (as opposed generically to the root dict-like object). if not argument.parameter.parse or not argument.field_info.required or argument.keys: continue if not argument.has_tokens: raise MissingArgumentError(argument=argument) bound = _bind(argument_collection, func) except CycloptsError as e: e.root_input_tokens = tokens e.unused_tokens = unused_tokens raise return bound, unused_tokens cyclopts-3.9.0/cyclopts/config/000077500000000000000000000000001475451620500165015ustar00rootroot00000000000000cyclopts-3.9.0/cyclopts/config/__init__.py000066400000000000000000000004411475451620500206110ustar00rootroot00000000000000__all__ = [ "ConfigFromFile", "Env", "Json", "Toml", "Yaml", ] from cyclopts.config._common import ConfigFromFile from cyclopts.config._env import Env from cyclopts.config._json import Json from cyclopts.config._toml import Toml from cyclopts.config._yaml import Yaml cyclopts-3.9.0/cyclopts/config/_common.py000066400000000000000000000204711475451620500205060ustar00rootroot00000000000000import errno import itertools import os from abc import ABC, abstractmethod from collections.abc import Iterable, Iterator from contextlib import suppress from itertools import chain from pathlib import Path from typing import TYPE_CHECKING, Any, Optional, Sequence, Union from attrs import define, field from cyclopts.argument import ArgumentCollection from cyclopts.exceptions import CycloptsError, UnknownOptionError from cyclopts.token import Token from cyclopts.utils import is_iterable, to_tuple_converter if TYPE_CHECKING: from cyclopts.core import App def _walk_leaves( d, parent_keys: Optional[tuple[str, ...]] = None, ) -> Iterator[tuple[tuple[str, ...], Any]]: if parent_keys is None: parent_keys = () if isinstance(d, dict): for key, value in d.items(): current_keys = parent_keys + (key,) if isinstance(value, dict): yield from _walk_leaves(value, current_keys) else: yield current_keys, value else: yield (), d def _meta_arguments(apps: Sequence["App"]) -> ArgumentCollection: argument_collection = ArgumentCollection() for i, app in enumerate(apps): if app._meta is None: continue argument_collection.extend(app._meta.assemble_argument_collection(apps=apps[:i])) return argument_collection class CacheKey: """Abstraction to quickly check if a file needs to be read again. If a newly instantiated ``CacheKey`` doesn't equal a previously instantiated ``CacheKey``, then the file needs to be re-read. """ def __init__(self, path: Union[str, Path]): self.path = Path(path).absolute() if self.path.exists(): stat = self.path.stat() self._mtime = stat.st_mtime self._size = stat.st_size else: self._mtime = None self._size = None def __eq__(self, other): if not isinstance(other, type(self)): return False return self._mtime == other._mtime and self._size == other._size and self.path == other.path def to_cli_option_name(*keys: str) -> str: return "--" + ".".join(keys) def update_argument_collection( config: dict, source: str, arguments: ArgumentCollection, apps: Optional[Sequence["App"]] = None, *, root_keys: Iterable[str], allow_unknown: bool, ): """Updates an argument collection if it doesn't already have tokens. Note: it feels bad that we're passing in ``apps`` here. """ meta_arguments = _meta_arguments(apps or ()) do_not_update = {} for option_key, option_value in config.items(): for subkeys, value in _walk_leaves(option_value): cli_option_name = to_cli_option_name(option_key, *subkeys) complete_keyword = "".join(f"[{k}]" for k in itertools.chain(root_keys, (option_key,), subkeys)) try: meta_arguments.match(cli_option_name) except ValueError: pass else: continue try: argument, remaining_keys, _ = arguments.match(cli_option_name) except ValueError: if allow_unknown: continue if apps and apps[-1]._meta_parent: # We're currently in the meta-app portion of the launch process, # so MOST supplied options will be unmatched, as we haven't gotten # to the actual command processing yet. continue raise UnknownOptionError( token=Token(keyword=complete_keyword, source=source), argument_collection=arguments ) from None if do_not_update.setdefault(id(argument), bool(argument.tokens)): # If this argument already has tokens on **first** access, then skip it. # Allows us to add multiple tokens to an argument from a **single** source (config file). continue # Convert all values to strings, so that the Cyclopts engine can process them. # This may (eventually) result in converting back to the original dtype. if not is_iterable(value): value = (value,) for i, v in enumerate(value): # TODO: is this index correct? If the source value is a list, it should probably be different if v is None: # Pass ``None`` as an implicit_value so it certainly gets interpreted as ``None`` later. token = Token( keyword=complete_keyword, implicit_value=None, source=source, index=i, keys=remaining_keys ) else: # Convert the value back into a string, so it can be re-converted. token = Token(keyword=complete_keyword, value=str(v), source=source, index=i, keys=remaining_keys) argument.append(token) @define class ConfigFromFile(ABC): path: Union[str, Path] = field(converter=Path) root_keys: Iterable[str] = field(default=(), converter=to_tuple_converter) must_exist: bool = field(default=False, kw_only=True) search_parents: bool = field(default=False, kw_only=True) allow_unknown: bool = field(default=False, kw_only=True) use_commands_as_keys: bool = field(default=True, kw_only=True) _config: Optional[dict[str, Any]] = field(default=None, init=False, repr=False) "Loaded configuration structure (to be loaded by subclassed ``_load_config`` method)." _config_cache_key: Optional[CacheKey] = field(default=None, init=False, repr=False) "Conditions under which ``_config`` was loaded." @abstractmethod def _load_config(self, path: Path) -> dict[str, Any]: """Load the config dictionary from path. Do **not** do any downstream caching; ``ConfigFromFile`` handles caching. Parameters ---------- path: Path Path to the file. Guaranteed to exist. Returns ------- dict Loaded configuration. """ raise NotImplementedError @property def config(self) -> dict[str, Any]: assert isinstance(self.path, Path) for parent in self.path.parents: candidate = parent / self.path.name if candidate.exists(): cache_key = CacheKey(candidate) if self._config_cache_key == cache_key: return self._config or {} try: self._config = self._load_config(candidate) self._config_cache_key = cache_key except CycloptsError: raise except Exception as e: msg = getattr(type(e), "__name__", "") with suppress(IndexError): exception_msg = e.args[0] if msg: msg += ": " msg += exception_msg raise CycloptsError(msg=msg) from e return self._config elif self.search_parents: # Continue iterating over parents. continue elif self.must_exist: raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self.path)) # No matching file was found. if self.must_exist: raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(self.path)) self._config = {} return self._config @property def source(self) -> str: return str(self.path) def __call__(self, apps: list["App"], commands: tuple[str, ...], arguments: ArgumentCollection): config: dict[str, Any] = self.config.copy() try: for key in chain(self.root_keys, commands if self.use_commands_as_keys else ()): config = config[key] except KeyError: return # Ignore keys that represent subcommands command_app = apps[-1] if self.use_commands_as_keys else apps[0] config = {k: v for k, v in config.items() if k not in command_app} assert isinstance(self.path, Path) source = str(self.path.absolute()) update_argument_collection( config, source, arguments, apps, root_keys=self.root_keys, allow_unknown=self.allow_unknown ) cyclopts-3.9.0/cyclopts/config/_env.py000066400000000000000000000030271475451620500200040ustar00rootroot00000000000000import os from typing import TYPE_CHECKING, Callable from attrs import define, field from cyclopts._env_var import env_var_split from cyclopts.argument import ArgumentCollection, Token if TYPE_CHECKING: from cyclopts.core import App @define class Env: prefix: str = "" command: bool = field(default=True, kw_only=True) split: Callable = field(default=env_var_split, kw_only=True) def __call__(self, apps: list["App"], commands: tuple[str, ...], arguments: "ArgumentCollection"): prefix = self.prefix if self.command and commands: prefix += "_".join(x.upper() for x in commands) + "_" candidate_env_keys = [x for x in os.environ if x.startswith(prefix)] candidate_env_keys.sort() for candidate_env_key in candidate_env_keys: try: argument, remaining_keys, _ = arguments.match( candidate_env_key[len(prefix) :], transform=lambda s: s.upper().replace("-", "_").replace(".", "_").lstrip("_"), delimiter="_", ) except ValueError: continue if any(x.source != "env" for x in argument.tokens): continue remaining_keys = tuple(x.lower() for x in remaining_keys) for i, value in enumerate(argument.env_var_split(os.environ[candidate_env_key])): argument.append( Token(keyword=candidate_env_key, value=value, source="env", index=i, keys=remaining_keys) ) cyclopts-3.9.0/cyclopts/config/_json.py000066400000000000000000000006541475451620500201700ustar00rootroot00000000000000from pathlib import Path from typing import Any from cyclopts.config._common import ConfigFromFile from cyclopts.exceptions import CoercionError class Json(ConfigFromFile): def _load_config(self, path: Path) -> dict[str, Any]: import json with path.open() as f: try: return json.load(f) except json.JSONDecodeError as e: raise CoercionError from e cyclopts-3.9.0/cyclopts/config/_toml.py000066400000000000000000000010511475451620500201620ustar00rootroot00000000000000from pathlib import Path from typing import Any from cyclopts.config._common import ConfigFromFile class Toml(ConfigFromFile): def _load_config(self, path: Path) -> dict[str, Any]: try: # Attempt to use builtin >=python3.11 import tomllib # pyright: ignore[reportMissingImports] except ImportError: # Fallback to most popular pypi toml package. import tomli as tomllib # pyright: ignore[reportMissingImports] with path.open("rb") as f: return tomllib.load(f) cyclopts-3.9.0/cyclopts/config/_yaml.py000066400000000000000000000005101475451620500201500ustar00rootroot00000000000000from pathlib import Path from typing import Any from cyclopts.config._common import ConfigFromFile class Yaml(ConfigFromFile): def _load_config(self, path: Path) -> dict[str, Any]: from yaml import safe_load # pyright: ignore[reportMissingImports] with path.open() as f: return safe_load(f) cyclopts-3.9.0/cyclopts/core.py000066400000000000000000001502221475451620500165400ustar00rootroot00000000000000import inspect import os import sys import traceback from collections.abc import Coroutine, Iterable, Iterator from contextlib import suppress from copy import copy from enum import Enum from functools import lru_cache, partial from itertools import chain from pathlib import Path from typing import ( TYPE_CHECKING, Annotated, Any, Callable, Literal, Optional, Sequence, TypeVar, Union, overload, ) from attrs import define, field import cyclopts.utils from cyclopts.annotations import resolve_annotated from cyclopts.argument import ArgumentCollection from cyclopts.bind import create_bound_arguments, is_option_like, normalize_tokens from cyclopts.exceptions import ( CommandCollisionError, CycloptsError, InvalidCommandError, UnknownOptionError, UnusedCliTokensError, ValidationError, format_cyclopts_error, ) from cyclopts.group import Group, sort_groups from cyclopts.group_extractors import groups_from_app, inverse_groups_from_app from cyclopts.help import ( HelpPanel, create_parameter_help_panel, format_command_entries, format_doc, format_str, format_usage, resolve_help_format, resolve_version_format, ) from cyclopts.parameter import Parameter, validate_command from cyclopts.protocols import Dispatcher from cyclopts.token import Token from cyclopts.utils import ( UNSET, default_name_transform, optional_to_tuple_converter, to_list_converter, to_tuple_converter, ) T = TypeVar("T", bound=Callable) V = TypeVar("V") with suppress(ImportError): # By importing, makes things like the arrow-keys work. # Not available on windows import readline # noqa: F401 if TYPE_CHECKING: from rich.console import Console class _CannotDeriveCallingModuleNameError(Exception): pass def _get_root_module_name(): """Get the calling package name from the call-stack.""" for elem in inspect.stack(): module = inspect.getmodule(elem.frame) if module is None: continue root_module_name = module.__name__.split(".")[0] if root_module_name == "cyclopts": continue return root_module_name raise _CannotDeriveCallingModuleNameError # pragma: no cover def _default_version(default="0.0.0") -> str: """Attempts to get the calling code's version. Returns ------- version: str ``default`` if it cannot determine version. """ import importlib if sys.version_info < (3, 10): # pragma: no cover from importlib_metadata import PackageNotFoundError from importlib_metadata import version as importlib_metadata_version else: # pragma: no cover from importlib.metadata import PackageNotFoundError from importlib.metadata import version as importlib_metadata_version try: root_module_name = _get_root_module_name() except _CannotDeriveCallingModuleNameError: # pragma: no cover return default # Attempt to get the Distribution Package’s version number. try: return importlib_metadata_version(root_module_name) except PackageNotFoundError: pass # Attempt packagename.__version__ # Not sure if this is redundant with ``importlib.metadata``, # but there's no real harm in checking. try: module = importlib.import_module(root_module_name) return module.__version__ except (ImportError, AttributeError): pass # Final fallback return default def _validate_default_command(x): if isinstance(x, App): raise TypeError("Cannot register a sub-App to default.") return x def _combined_meta_command_mapping( app: Optional["App"], recurse_meta=True, recurse_parent_meta=True ) -> dict[str, "App"]: """Return a copied and combined mapping containing app and meta-app commands.""" if app is None: return {} command_mapping = copy(app._commands) if recurse_meta: command_mapping.update(_combined_meta_command_mapping(app._meta)) if recurse_parent_meta and app._meta_parent: command_mapping.update(_combined_meta_command_mapping(app._meta_parent, recurse_meta=False)) return command_mapping def _get_command_groups(parent_app: "App", child_app: "App"): """Extract out the command groups from the ``parent_app`` for a given ``child_app``.""" return next(x for x in inverse_groups_from_app(parent_app) if x[0] is child_app)[1] def resolve_default_parameter_from_apps(apps: Optional[Sequence["App"]]) -> Parameter: """The default_parameter resolution depends on the parent-child path traversed.""" if not apps: return Parameter() cparams = [] for parent_app, child_app in zip(apps[:-1], apps[1:]): # child_app could be a command of parent_app.meta if parent_app._meta and child_app in parent_app._meta.subapps: cparams = [] # meta-apps do NOT inherit from their parenting app. parent_app = parent_app._meta groups = _get_command_groups(parent_app, child_app) cparams.extend([group.default_parameter for group in groups]) cparams.append(parent_app.default_parameter) cparams.append(apps[-1].default_parameter) return Parameter.combine(*cparams) def _walk_metas(app: "App"): # Iterates from deepest to shallowest meta-apps meta_list = [app] # shallowest to deepest meta = app while (meta := meta._meta) and meta.default_command: meta_list.append(meta) yield from reversed(meta_list) def _group_converter(input_value: Union[None, str, Group]) -> Optional[Group]: if input_value is None: return None elif isinstance(input_value, str): return Group(input_value) elif isinstance(input_value, Group): return input_value else: raise TypeError @define class App: # This can ONLY ever be Tuple[str, ...] due to converter. # The other types is to make mypy happy for Cyclopts users. _name: Union[None, str, tuple[str, ...]] = field(default=None, alias="name", converter=optional_to_tuple_converter) _help: Optional[str] = field(default=None, alias="help") usage: Optional[str] = field(default=None) # Everything below must be kw_only default_command: Optional[Callable] = field(default=None, converter=_validate_default_command, kw_only=True) default_parameter: Optional[Parameter] = field(default=None, kw_only=True) # This can ONLY ever be None or Tuple[Callable, ...] _config: Union[None, Callable, Iterable[Callable]] = field( default=None, alias="config", converter=optional_to_tuple_converter, kw_only=True, ) version: Union[None, str, Callable[..., str]] = field(default=_default_version, kw_only=True) # This can ONLY ever be a Tuple[str, ...] _version_flags: Union[str, Iterable[str]] = field( default=["--version"], converter=to_tuple_converter, alias="version_flags", kw_only=True, ) show: bool = field(default=True, kw_only=True) console: Optional["Console"] = field(default=None, kw_only=True) # This can ONLY ever be a Tuple[str, ...] _help_flags: Union[str, Iterable[str]] = field( default=["--help", "-h"], converter=to_tuple_converter, alias="help_flags", kw_only=True, ) help_format: Optional[ Literal[ "markdown", "md", "plaintext", "restructuredtext", "rst", "rich", ] ] = field(default=None, kw_only=True) help_on_error: Optional[bool] = field(default=None, kw_only=True) version_format: Optional[ Literal[ "markdown", "md", "plaintext", "restructuredtext", "rst", "rich", ] ] = field(default=None, kw_only=True) # This can ONLY ever be Tuple[Union[Group, str], ...] due to converter. # The other types is to make mypy happy for Cyclopts users. group: Union[Group, str, tuple[Union[Group, str], ...]] = field( default=None, converter=to_tuple_converter, kw_only=True ) # This can ONLY ever be a Group or None _group_arguments: Union[Group, str, None] = field( alias="group_arguments", default=None, converter=_group_converter, kw_only=True, ) # This can ONLY ever be a Group or None _group_parameters: Union[Group, str, None] = field( alias="group_parameters", default=None, converter=_group_converter, kw_only=True, ) # This can ONLY ever be a Group or None _group_commands: Union[Group, str, None] = field( alias="group_commands", default=None, converter=_group_converter, kw_only=True, ) validator: list[Callable[..., Any]] = field(default=None, converter=to_list_converter, kw_only=True) _name_transform: Optional[Callable[[str], str]] = field( default=None, alias="name_transform", kw_only=True, ) _sort_key: Any = field( default=None, alias="sort_key", converter=lambda x: UNSET if x is None else x, kw_only=True, ) end_of_options_delimiter: Optional[str] = field(default=None, kw_only=True) ###################### # Private Attributes # ###################### # Maps CLI-name of a command to a function handle. _commands: dict[str, "App"] = field(init=False, factory=dict) _parents: list["App"] = field(init=False, factory=list) _meta: Optional["App"] = field(init=False, default=None) _meta_parent: Optional["App"] = field(init=False, default=None) def __attrs_post_init__(self): # Trigger the setters self.help_flags = self._help_flags self.version_flags = self._version_flags ########### # Methods # ########### def _delete_commands(self, commands: Iterable[str]): """Safely delete commands. Will **not** raise an exception if command(s) do not exist. Parameters ---------- commands: Iterable[str, ...] Strings of commands to delete. """ # Remove all the old version-flag commands. for command in commands: try: del self[command] except KeyError: pass @property def version_flags(self): return self._version_flags @version_flags.setter def version_flags(self, value): self._version_flags = value self._delete_commands(self._version_flags) if self._version_flags: self.command( self.version_print, name=self._version_flags, help_flags=[], version_flags=[], version=self.version, help="Display application version.", ) @property def help_flags(self): return self._help_flags @help_flags.setter def help_flags(self, value): self._help_flags = value self._delete_commands(self._help_flags) if self._help_flags: self.command( self.help_print, name=self._help_flags, help_flags=[], version_flags=[], version=self.version, help="Display this message and exit.", ) @property def name(self) -> tuple[str, ...]: """Application name(s). Dynamically derived if not previously set.""" if self._name: return self._name # pyright: ignore[reportReturnType] elif self.default_command is None: name = Path(sys.argv[0]).name if name == "__main__.py": name = _get_root_module_name() return (name,) else: return (self.name_transform(self.default_command.__name__),) @property def group_arguments(self): if self._group_arguments is None: return Group.create_default_arguments() return self._group_arguments @group_arguments.setter def group_arguments(self, value): self._group_arguments = value @property def group_parameters(self): if self._group_parameters is None: return Group.create_default_parameters() return self._group_parameters @group_parameters.setter def group_parameters(self, value): self._group_parameters = value @property def group_commands(self): if self._group_commands is None: return Group.create_default_commands() return self._group_commands @group_commands.setter def group_commands(self, value): self._group_commands = value @property def config(self) -> tuple[str, ...]: return self._resolve(None, None, "_config") # pyright: ignore[reportReturnType] @config.setter def config(self, value): self._config = value @property def help(self) -> str: if self._help is not None: return self._help elif self.default_command is None: # Try and fallback to a meta-app docstring. if self._meta is None: return "" else: return self.meta.help elif self.default_command.__doc__ is None: return "" else: return self.default_command.__doc__ @help.setter def help(self, value): self._help = value @property def name_transform(self): return self._name_transform if self._name_transform else default_name_transform @name_transform.setter def name_transform(self, value): self._name_transform = value @property def sort_key(self): return None if self._sort_key is UNSET else self._sort_key def version_print( self, console: Optional["Console"] = None, ) -> None: """Print the application version. Parameters ---------- console: rich.console.Console Console to print version string to. If not provided, follows the resolution order defined in :attr:`App.console`. """ console = self._resolve_console(None, console) version_format = resolve_version_format([self]) version_raw = self.version() if callable(self.version) else self.version if version_raw is None: version_raw = "0.0.0" version_formatted = format_str(version_raw, format=version_format) console.print(version_formatted) @property def subapps(self): for k in self: yield self[k] def __getitem__(self, key: str) -> "App": """Get the subapp from a command string. All commands get registered to Cyclopts as subapps. The actual function handler is at ``app[key].default_command``. Example usage: .. code-block:: python from cyclopts import App app = App() app.command(App(name="foo")) @app["foo"].command def bar(): print("Running bar.") app() """ return self._get_item(key) def _get_item(self, key, recurse_meta=True) -> "App": if recurse_meta and self._meta: with suppress(KeyError): return self.meta[key] if self._meta_parent: with suppress(KeyError): return self._meta_parent._get_item(key, recurse_meta=False) return self._commands[key] def __delitem__(self, key: str): del self._commands[key] def __contains__(self, k: str) -> bool: if k in self._commands: return True if self._meta_parent: return k in self._meta_parent return False def __iter__(self) -> Iterator[str]: """Iterate over command & meta command names. Example usage: .. code-block:: python from cyclopts import App app = App() @app.command def foo(): pass @app.command def bar(): pass # help and version flags are treated as commands. assert list(app) == ["--help", "-h", "--version", "foo", "bar"] """ commands = list(self._commands) yield from commands commands = set(commands) if self._meta_parent: for command in self._meta_parent: if command not in commands: yield command @property def meta(self) -> "App": if self._meta is None: self._meta = type(self)( help_flags=self.help_flags, version_flags=self.version_flags, group_commands=copy(self._group_commands), group_arguments=copy(self._group_arguments), group_parameters=copy(self._group_parameters), ) self._meta._meta_parent = self return self._meta def parse_commands( self, tokens: Union[None, str, Iterable[str]] = None, *, include_parent_meta=True, ) -> tuple[tuple[str, ...], tuple["App", ...], list[str]]: """Extract out the command tokens from a command. You are probably actually looking for :meth:`parse_args`. Parameters ---------- tokens: Union[None, str, Iterable[str]] Either a string, or a list of strings to launch a command. Defaults to ``sys.argv[1:]`` Returns ------- List[str] Strings that are interpreted as a valid command chain. List[App] The associated :class:`App` object for each element in the command chain. List[str] The remaining non-command tokens. """ tokens = normalize_tokens(tokens) command_chain = [] app = self apps: list[App] = [app] unused_tokens = tokens command_mapping = _combined_meta_command_mapping(app, recurse_parent_meta=include_parent_meta) for i, token in enumerate(tokens): try: app = command_mapping[token] apps.append(app) unused_tokens = tokens[i + 1 :] except KeyError: break command_chain.append(token) command_mapping = _combined_meta_command_mapping(app, recurse_parent_meta=include_parent_meta) return tuple(command_chain), tuple(apps), unused_tokens # This overload is used in code like: # # @app.command # def my_command(foo: str): # ... @overload def command( # pragma: no cover self, obj: T, name: Union[None, str, Iterable[str]] = None, **kwargs: object, ) -> T: ... # This overload is used in code like: # # @app.command(name="bar") # def my_command(foo: str): # ... @overload def command( # pragma: no cover self, obj: None = None, name: Union[None, str, Iterable[str]] = None, **kwargs: object, ) -> Callable[[T], T]: ... def command( self, obj: Optional[T] = None, name: Union[None, str, Iterable[str]] = None, **kwargs: object, ) -> Union[T, Callable[[T], T]]: """Decorator to register a function as a CLI command. Example usage: .. code-block:: from cyclopts import App app = App() @app.command def foo(): print("foo!") @app.command(name="buzz") def bar(): print("bar!") app() .. code-block:: console $ my-script foo foo! $ my-script buzz bar! Parameters ---------- obj: Optional[Callable] Function or :class:`App` to be registered as a command. name: Union[None, str, Iterable[str]] Name(s) to register the command to. If not provided, defaults to: * If registering an :class:`App`, then the app's name. * If registering a **function**, then the function's name after applying :attr:`name_transform`. `**kwargs` Any argument that :class:`App` can take. """ if obj is None: # Called ``@app.command(...)`` return partial(self.command, name=name, **kwargs) # pyright: ignore[reportReturnType] if isinstance(obj, App): app = obj if app._name is None and name is None: raise ValueError("Sub-app MUST have a name specified.") if kwargs: raise ValueError("Cannot supplied additional configuration when registering a sub-App.") if app._group_commands is None: app._group_commands = copy(self._group_commands) if app._group_parameters is None: app._group_parameters = copy(self._group_parameters) if app._group_arguments is None: app._group_arguments = copy(self._group_arguments) else: kwargs.setdefault("help_flags", self.help_flags) kwargs.setdefault("version_flags", self.version_flags) if "group_commands" not in kwargs: kwargs["group_commands"] = copy(self._group_commands) if "group_parameters" not in kwargs: kwargs["group_parameters"] = copy(self._group_parameters) if "group_arguments" not in kwargs: kwargs["group_arguments"] = copy(self._group_arguments) app = App(default_command=obj, **kwargs) # pyright: ignore for flag in chain(kwargs["help_flags"], kwargs["version_flags"]): # pyright: ignore app[flag].show = False if app._name_transform is None: app.name_transform = self.name_transform if name is None: name = app.name else: app._name = name # pyright: ignore[reportAttributeAccessIssue] for n in to_tuple_converter(name): if n in self: raise CommandCollisionError(f'Command "{n}" already registered.') # Warning: app._name may not align with command name self._commands[n] = app app._parents.append(self) return obj # pyright: ignore[reportReturnType] # This overload is used in code like: # # @app.default # def my_command(foo: str): # ... @overload def default( # pragma: no cover self, obj: T, *, validator: Optional[Callable[..., Any]] = None, ) -> T: ... # This overload is used in code like: # # @app.default() # def my_command(foo: str): # ... @overload def default( # pragma: no cover self, obj: None = None, *, validator: Optional[Callable[..., Any]] = None, ) -> Callable[[T], T]: ... def default( self, obj: Optional[T] = None, *, validator: Optional[Callable[..., Any]] = None, ) -> Union[T, Callable[[T], T]]: """Decorator to register a function as the default action handler. Example usage: .. code-block:: python from cyclopts import App app = App() @app.default def main(): print("Hello world!") app() .. code-block:: console $ my-script Hello world! """ if obj is None: # Called ``@app.default_command(...)`` return partial(self.default, validator=validator) # pyright: ignore[reportReturnType] if isinstance(obj, App): # Registering a sub-App raise TypeError("Cannot register a sub-App to default.") if self.default_command is not None: raise CommandCollisionError(f"Default command previously set to {self.default_command}.") self.default_command = obj if validator: self.validator = validator # pyright: ignore[reportAttributeAccessIssue] return obj def assemble_argument_collection( self, *, apps: Optional[Sequence["App"]] = None, default_parameter: Optional[Parameter] = None, parse_docstring: bool = False, ) -> ArgumentCollection: """Assemble the argument collection for this app. Parameters ---------- apps: Optional[Sequence[App]] List of parenting apps that lead to this app. If provided, will resolve ``default_parameter`` from the apps. default_parameter: Optional[Parameter] Default parameter with highest priority. parse_docstring: bool Parse the docstring of :attr:`default_command`. Set to :obj:`True` if we need help strings, otherwise set to :obj:`False` for performance reasons. Returns ------- ArgumentCollection All arguments for this app. """ return ArgumentCollection._from_callable( self.default_command, # pyright: ignore Parameter.combine(resolve_default_parameter_from_apps(apps), self.default_parameter, default_parameter), group_arguments=self._group_arguments, # pyright: ignore group_parameters=self._group_parameters, # pyright: ignore parse_docstring=parse_docstring, ) def parse_known_args( self, tokens: Union[None, str, Iterable[str]] = None, *, console: Optional["Console"] = None, end_of_options_delimiter: Optional[str] = None, ) -> tuple[Callable, inspect.BoundArguments, list[str], dict[str, Any]]: """Interpret arguments into a registered function, :class:`~inspect.BoundArguments`, and any remaining unknown tokens. Parameters ---------- tokens: Union[None, str, Iterable[str]] Either a string, or a list of strings to launch a command. Defaults to ``sys.argv[1:]`` console: rich.console.Console Console to print help and runtime Cyclopts errors. If not provided, follows the resolution order defined in :attr:`App.console`. end_of_options_delimiter: Optional[str] All tokens after this delimiter will be force-interpreted as positional arguments. If :obj:`None`, fallback to :class:`App.end_of_options_delimiter`. If that is not set, it will default to POSIX-standard ``"--"``. Set to an empty string to disable. Returns ------- command: Callable Bare function to execute. bound: inspect.BoundArguments Bound arguments for ``command``. unused_tokens: List[str] Any remaining CLI tokens that didn't get parsed for ``command``. ignored: dict[str, Any] A mapping of python-variable-name to annotated type of any parameter with annotation ``parse=False``. :obj:`~typing.Annotated` will be resolved. Intended to simplify :ref:`meta apps `. """ command, bound, unused_tokens, ignored, argument_collection = self._parse_known_args( tokens, console=console, end_of_options_delimiter=end_of_options_delimiter ) return command, bound, unused_tokens, ignored def _parse_known_args( self, tokens: Union[None, str, Iterable[str]] = None, *, console: Optional["Console"], end_of_options_delimiter: Optional[str], ) -> tuple[Callable, inspect.BoundArguments, list[str], dict[str, Any], ArgumentCollection]: if tokens is None: _log_framework_warning(_detect_test_framework()) tokens = normalize_tokens(tokens) meta_parent = self command_chain, apps, unused_tokens = self.parse_commands(tokens, include_parent_meta=False) command_app = apps[-1] ignored: dict[str, Any] = {} # We don't want the command_app to be the version/help handler. with suppress(IndexError): if set(command_app.name) & set(apps[-2].help_flags + apps[-2].version_flags): # pyright: ignore apps = apps[:-1] command_app = apps[-1] try: parent_app = apps[-2] except IndexError: parent_app = None config: tuple[Callable, ...] = self._resolve(apps, None, "_config") or () config = tuple(partial(x, apps, command_chain) for x in config) end_of_options_delimiter = self._resolve(apps, end_of_options_delimiter, "end_of_options_delimiter") if end_of_options_delimiter is None: end_of_options_delimiter = "--" # Special flags (help/version) get intercepted by the root app. # Special flags are allows to be **anywhere** in the token stream. help_flag_index = _get_help_flag_index(tokens, command_app.help_flags) if help_flag_index is not None: tokens.pop(help_flag_index) help_flag_index = _get_help_flag_index(unused_tokens, command_app.help_flags) if help_flag_index is not None: unused_tokens.pop(help_flag_index) if unused_tokens and not command_app.default_command: raise InvalidCommandError(unused_tokens=unused_tokens) command = self.help_print while meta_parent := meta_parent._meta_parent: command = meta_parent.help_print bound = cyclopts.utils.signature(command).bind(tokens, console=console) unused_tokens = [] argument_collection = ArgumentCollection() elif any(flag in tokens for flag in command_app.version_flags): # Version command = self.version_print while meta_parent := meta_parent._meta_parent: command = meta_parent.version_print bound = cyclopts.utils.signature(command).bind() unused_tokens = [] argument_collection = ArgumentCollection() else: try: if command_app.default_command: command = command_app.default_command validate_command(command) argument_collection = command_app.assemble_argument_collection(apps=apps) ignored: dict[str, Any] = { argument.field_info.name: resolve_annotated(argument.field_info.annotation) for argument in argument_collection.filter_by(parse=False) } # We want the resolved group that ``app`` belongs to. command_groups = [] if parent_app is None else _get_command_groups(parent_app, command_app) bound, unused_tokens = create_bound_arguments( command_app.default_command, argument_collection, unused_tokens, config, end_of_options_delimiter=end_of_options_delimiter, ) try: for validator in command_app.validator: validator(**bound.arguments) except (AssertionError, ValueError, TypeError) as e: raise ValidationError(exception_message=e.args[0] if e.args else "", app=command_app) from e try: for command_group in command_groups: for validator in command_group.validator: # pyright: ignore validator(**bound.arguments) except (AssertionError, ValueError, TypeError) as e: raise ValidationError( exception_message=e.args[0] if e.args else "", group=command_group, # pyright: ignore ) from e else: if unused_tokens: raise InvalidCommandError(unused_tokens=unused_tokens) else: # Running the application with no arguments and no registered # ``default_command`` will default to ``help_print``. command = self.help_print bound = cyclopts.utils.signature(command).bind(tokens=tokens, console=console) unused_tokens = [] argument_collection = ArgumentCollection() except CycloptsError as e: e.target = command_app.default_command e.app = command_app if command_chain: e.command_chain = command_chain if e.console is None: e.console = self._resolve_console(tokens, console) raise return command, bound, unused_tokens, ignored, argument_collection def parse_args( self, tokens: Union[None, str, Iterable[str]] = None, *, console: Optional["Console"] = None, print_error: bool = True, exit_on_error: bool = True, help_on_error: Optional[bool] = None, verbose: bool = False, end_of_options_delimiter: Optional[str] = None, ) -> tuple[Callable, inspect.BoundArguments, dict[str, Any]]: """Interpret arguments into a function and :class:`~inspect.BoundArguments`. Raises ------ UnusedCliTokensError If any tokens remain after parsing. Parameters ---------- tokens: Union[None, str, Iterable[str]] Either a string, or a list of strings to launch a command. Defaults to ``sys.argv[1:]``. console: rich.console.Console Console to print help and runtime Cyclopts errors. If not provided, follows the resolution order defined in :attr:`App.console`. print_error: bool Print a rich-formatted error on error. Defaults to :obj:`True`. exit_on_error: bool If there is an error parsing the CLI tokens invoke ``sys.exit(1)``. Otherwise, continue to raise the exception. Defaults to :obj:`True`. help_on_error: bool Prints the help-page before printing an error, overriding :attr:`App.help_on_error`. Defaults to :obj:`None` (interpret from :class:`.App`, eventually defaulting to :obj:`False`). verbose: bool Populate exception strings with more information intended for developers. Defaults to :obj:`False`. end_of_options_delimiter: Optional[str] All tokens after this delimiter will be force-interpreted as positional arguments. If :obj:`None`, fallback to :class:`App.end_of_options_delimiter`. If that is not set, it will default to POSIX-standard ``"--"``. Set to an empty string to disable. Returns ------- command: Callable Function associated with command action. bound: inspect.BoundArguments Parsed and converted ``args`` and ``kwargs`` to be used when calling ``command``. ignored: dict[str, Any] A mapping of python-variable-name to type-hint of any parameter with annotation ``parse=False``. :obj:`~typing.Annotated` will be resolved. Intended to simplify :ref:`meta apps `. """ if tokens is None: _log_framework_warning(_detect_test_framework()) tokens = normalize_tokens(tokens) help_on_error = self._resolve(tokens, help_on_error, "help_on_error") or False # Normal parsing try: command, bound, unused_tokens, ignored, argument_collection = self._parse_known_args( tokens, console=console, end_of_options_delimiter=end_of_options_delimiter ) if unused_tokens: for token in unused_tokens: if is_option_like(token): token = token.split("=")[0] raise UnknownOptionError( token=Token(keyword=token, source="cli"), argument_collection=argument_collection ) raise UnusedCliTokensError( target=command, unused_tokens=unused_tokens, ) except CycloptsError as e: e.verbose = verbose e.root_input_tokens = tokens if e.console is None: e.console = self._resolve_console(tokens, console) if help_on_error: assert e.console self.help_print(tokens, console=e.console) if print_error: assert e.console e.console.print(format_cyclopts_error(e)) if exit_on_error: sys.exit(1) raise return command, bound, ignored def __call__( self, tokens: Union[None, str, Iterable[str]] = None, *, console: Optional["Console"] = None, print_error: bool = True, exit_on_error: bool = True, help_on_error: Optional[bool] = None, verbose: bool = False, end_of_options_delimiter: Optional[str] = None, ): """Interprets and executes a command. Parameters ---------- tokens : Union[None, str, Iterable[str]] Either a string, or a list of strings to launch a command. Defaults to ``sys.argv[1:]``. console: rich.console.Console Console to print help and runtime Cyclopts errors. If not provided, follows the resolution order defined in :attr:`App.console`. print_error: bool Print a rich-formatted error on error. Defaults to :obj:`True`. exit_on_error: bool If there is an error parsing the CLI tokens invoke ``sys.exit(1)``. Otherwise, continue to raise the exception. Defaults to ``True``. help_on_error: bool Prints the help-page before printing an error, overriding :attr:`App.help_on_error`. Defaults to :obj:`None` (interpret from :class:`.App`, eventually defaulting to :obj:`False`). verbose: bool Populate exception strings with more information intended for developers. Defaults to :obj:`False`. end_of_options_delimiter: Optional[str] All tokens after this delimiter will be force-interpreted as positional arguments. If :obj:`None`, fallback to :class:`App.end_of_options_delimiter`. If that is not set, it will default to POSIX-standard ``"--"``. Returns ------- return_value: Any The value the command function returns. """ if tokens is None: _log_framework_warning(_detect_test_framework()) tokens = normalize_tokens(tokens) command, bound, _ = self.parse_args( tokens, console=console, print_error=print_error, exit_on_error=exit_on_error, help_on_error=help_on_error, verbose=verbose, end_of_options_delimiter=end_of_options_delimiter, ) try: if inspect.iscoroutinefunction(command): import asyncio return asyncio.run(command(*bound.args, **bound.kwargs)) else: return command(*bound.args, **bound.kwargs) except Exception as e: try: from pydantic import ValidationError as PydanticValidationError except ImportError: PydanticValidationError = None # noqa: N806 if PydanticValidationError is not None and isinstance(e, PydanticValidationError): if print_error: console = self._resolve_console(tokens, console) console.print(format_cyclopts_error(e)) if exit_on_error: sys.exit(1) raise def _resolve(self, tokens_or_apps: Optional[Sequence], override: Optional[V], attribute: str) -> Optional[V]: if override is not None: return override if not tokens_or_apps: apps = (self,) elif isinstance(tokens_or_apps[0], App): apps = tokens_or_apps else: _, apps, _ = self.parse_commands(tokens_or_apps) for app in reversed(apps): result = getattr(app, attribute) if result is not None: return result # Check parenting meta app(s) meta_app = app while (meta_app := meta_app._meta_parent) is not None: result = getattr(meta_app, attribute) if result is not None: return result return None def _resolve_console(self, tokens_or_apps: Optional[Sequence], override: Optional["Console"] = None) -> "Console": result = self._resolve(tokens_or_apps, override, "console") if result is not None: return result from rich.console import Console return Console() def help_print( self, tokens: Annotated[Union[None, str, Iterable[str]], Parameter(show=False)] = None, *, console: Annotated[Optional["Console"], Parameter(parse=False)] = None, ) -> None: """Print the help page. Parameters ---------- tokens: Union[None, str, Iterable[str]] Tokens to interpret for traversing the application command structure. If not provided, defaults to ``sys.argv``. console: rich.console.Console Console to print help and runtime Cyclopts errors. If not provided, follows the resolution order defined in :attr:`App.console`. """ tokens = normalize_tokens(tokens) command_chain, apps, _ = self.parse_commands(tokens) executing_app = apps[-1] console = self._resolve_console(tokens, console) # Print the: # my-app command COMMAND [ARGS] [OPTIONS] if executing_app.usage is None: console.print(format_usage(self, command_chain)) elif executing_app.usage: # i.e. skip empty-string. console.print(executing_app.usage + "\n") # Print the App/Command's Doc String. help_format = resolve_help_format(apps) console.print(format_doc(executing_app, help_format)) for help_panel in self._assemble_help_panels(tokens, help_format): console.print(help_panel) def _assemble_help_panels( self, tokens: Union[None, str, Iterable[str]], help_format, ) -> list[HelpPanel]: from rich.console import Group as RichGroup from rich.console import NewLine _, apps, _ = self.parse_commands(tokens) help_format = resolve_help_format(apps) panels: dict[str, tuple[Group, HelpPanel]] = {} # Handle commands first; there's an off chance they may be "upgraded" # to an argument/parameter panel. for subapp in _walk_metas(apps[-1]): # Handle Commands for group, elements in groups_from_app(subapp): if not group.show: continue try: _, command_panel = panels[group.name] except KeyError: command_panel = HelpPanel( format="command", title=group.name, ) panels[group.name] = (group, command_panel) if group.help: group_help = format_str(group.help, format=help_format) if command_panel.description: command_panel.description = RichGroup(command_panel.description, NewLine(), group_help) else: command_panel.description = group_help command_panel.entries.extend(format_command_entries(elements, format=help_format)) # Handle Arguments/Parameters for subapp in _walk_metas(apps[-1]): if not subapp.default_command: continue argument_collection = subapp.assemble_argument_collection(apps=apps, parse_docstring=True) for group in argument_collection.groups: if not group.show: continue group_argument_collection = argument_collection.filter_by(group=group) if not group_argument_collection: continue try: _, existing_panel = panels[group.name] except KeyError: existing_panel = None new_panel = create_parameter_help_panel(group, group_argument_collection, help_format) if existing_panel: # An imperfect merging process existing_panel.format = "parameter" existing_panel.entries = new_panel.entries + existing_panel.entries # Commands go last if new_panel.description: if existing_panel.description: existing_panel.description = RichGroup( existing_panel.description, NewLine(), new_panel.description ) else: existing_panel.description = new_panel.description else: panels[group.name] = (group, new_panel) groups = [x[0] for x in panels.values()] help_panels = [x[1] for x in panels.values()] out = [] for help_panel in sort_groups(groups, help_panels)[1]: help_panel.remove_duplicates() if help_panel.format == "command": # don't sort format == "parameter" because order may matter there! help_panel.sort() out.append(help_panel) return out def interactive_shell( self, prompt: str = "$ ", quit: Union[None, str, Iterable[str]] = None, dispatcher: Optional[Dispatcher] = None, **kwargs, ) -> None: """Create a blocking, interactive shell. All registered commands can be executed in the shell. Parameters ---------- prompt: str Shell prompt. Defaults to ``"$ "``. quit: Union[str, Iterable[str]] String or list of strings that will cause the shell to exit and this method to return. Defaults to ``["q", "quit"]``. dispatcher: Optional[Dispatcher] Optional function that subsequently invokes the command. The ``dispatcher`` function must have signature: .. code-block:: python def dispatcher(command: Callable, bound: inspect.BoundArguments) -> Any: return command(*bound.args, **bound.kwargs) The above is the default dispatcher implementation. `**kwargs` Get passed along to :meth:`parse_args`. """ if os.name == "posix": # pragma: no cover # Mac/Linux print("Interactive shell. Press Ctrl-D to exit.") else: # pragma: no cover # Windows print("Interactive shell. Press Ctrl-Z followed by Enter to exit.") if quit is None: quit = ["q", "quit"] if isinstance(quit, str): quit = [quit] def default_dispatcher(command, bound, _): return command(*bound.args, **bound.kwargs) if dispatcher is None: dispatcher = default_dispatcher kwargs.setdefault("exit_on_error", False) while True: try: user_input = input(prompt) except EOFError: # pragma: no cover break tokens = normalize_tokens(user_input) if not tokens: continue if tokens[0] in quit: break try: command, bound, ignored = self.parse_args(tokens, **kwargs) dispatcher(command, bound, ignored) except CycloptsError: # Upstream ``parse_args`` already printed the error pass except Exception: print(traceback.format_exc()) def update(self, app: "App"): """Copy over all commands from another :class:`App`. Commands from the meta app will **not** be copied over. Parameters ---------- app: cyclopts.App All commands from this application will be copied over. """ self._commands.update(app._commands) def __repr__(self): """Only shows non-default values.""" non_defaults = {} for a in self.__attrs_attrs__: # pyright: ignore[reportAttributeAccessIssue] if not a.init: continue v = getattr(self, a.name) # Compare types first because of some weird attribute issues. if type(v) != type(a.default) or v != a.default: # noqa: E721 non_defaults[a.alias] = v signature = ", ".join(f"{k}={v!r}" for k, v in non_defaults.items()) return f"{type(self).__name__}({signature})" def _get_help_flag_index(tokens, help_flags) -> Optional[int]: for help_flag in help_flags: with suppress(ValueError): index = tokens.index(help_flag) break else: index = None return index class TestFramework(str, Enum): UNKNOWN = "" PYTEST = "pytest" @lru_cache # Will always be the same for a given session. def _detect_test_framework() -> TestFramework: """Detects if we are currently being ran in a test framework. Returns ------- TestFramework Name of the testing framework. Returns an empty string if not testing framework discovered. """ if os.environ.get("PYTEST_VERSION") is not None: # Available as of pytest v8.2.0 (Apr 27, 2024) return TestFramework.PYTEST else: return TestFramework.UNKNOWN @lru_cache # Prevent logging of multiple warnings def _log_framework_warning(framework: TestFramework) -> None: """Log a warning message for a given testing framework. Intended to catch developers invoking their app during unit-tests without providing commands and erroneously reading from :obj:`sys.argv`. TO ONLY BE CALLED WITHIN A CYCLOPTS.APP METHOD. """ if framework == TestFramework.UNKNOWN: return import warnings for elem in inspect.stack(): frame = elem.frame f_back = frame.f_back calling_module = inspect.getmodule(f_back) if calling_module is None or f_back is None: continue calling_module_name = calling_module.__name__.split(".")[0] if calling_module_name == "cyclopts": continue # The "self" is within the Cyclopts codebase App.ANY_METHOD_HERE, # so this is a safe lookup. called_cyclopts_app_instance = frame.f_locals["self"] # Find the variable name in the previous frame that references this object candidate_variables = {**f_back.f_globals, **f_back.f_locals} matched_app_variables = [] for var_name, var_instance in candidate_variables.items(): if var_instance is called_cyclopts_app_instance: matched_app_variables.append(var_name) if len(matched_app_variables) != 1: # We could not determine the exact variable name; just call it app var_name = "app" else: var_name = matched_app_variables[0] message = f'Cyclopts application invoked without tokens under unit-test framework "{framework.value}". Did you mean "{var_name}([])"?' warnings.warn(UserWarning(message), stacklevel=3) break @overload def run(callable: Callable[..., Coroutine[None, None, V]], /) -> V: ... @overload def run(callable: Callable[..., V], /) -> V: ... def run(callable, /): """Run the given callable as a CLI command and return its result. The callable may also be a coroutine function. This function is syntax sugar for very simple use cases, and is roughly equivalent to: .. code-block:: python from cyclopts import App app = App() app.default(callable) app() Example usage: .. code-block:: python import cyclopts def main(name: str, age: int): print(f"Hello {name}, you are {age} years old.") cyclopts.run(main) """ app = App() app.default(callable) return app() cyclopts-3.9.0/cyclopts/exceptions.py000066400000000000000000000314521475451620500177740ustar00rootroot00000000000000import inspect from itertools import chain from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Sequence, get_args, get_origin from attrs import define, field import cyclopts.utils from cyclopts.annotations import get_hint_name from cyclopts.group import Group from cyclopts.token import Token from cyclopts.utils import is_option_like, json_decode_error_verbosifier if TYPE_CHECKING: from rich.console import Console from cyclopts.argument import Argument, ArgumentCollection from cyclopts.core import App __all__ = [ "CoercionError", "CommandCollisionError", "CycloptsError", "DocstringError", "InvalidCommandError", "MissingArgumentError", "MixedArgumentError", "RepeatArgumentError", "UnknownOptionError", "UnusedCliTokensError", "ValidationError", "format_cyclopts_error", ] def _get_function_info(func): return inspect.getsourcefile(func), inspect.getsourcelines(func)[1] class CommandCollisionError(Exception): """A command with the same name has already been registered to the app.""" # This doesn't derive from CycloptsError since this is a developer error # rather than a runtime error. class DocstringError(Exception): """The docstring either has a syntax error, or inconsistency with the function signature.""" @define(kw_only=True) class CycloptsError(Exception): """Root exception for runtime errors. As CycloptsErrors bubble up the Cyclopts call-stack, more information is added to it. Finally, :meth:`format_cyclopts_error` formats the message nicely for the user. """ msg: Optional[str] = None """ If set, override automatic message generation. """ verbose: bool = True """ More verbose error messages; aimed towards developers debugging their Cyclopts app. Defaults to ``False``. """ root_input_tokens: Optional[list[str]] = None """ The parsed CLI tokens that were initially fed into the :class:`App`. """ unused_tokens: Optional[list[str]] = None """ Leftover tokens after parsing is complete. """ target: Optional[Callable] = None """ The python function associated with the command being parsed. """ argument: Optional["Argument"] = None """ :class:`Argument` that was matched. """ command_chain: Optional[Sequence[str]] = None """ List of command that lead to ``target``. """ app: Optional["App"] = None """ The Cyclopts application itself. """ console: Optional["Console"] = field(default=None, kw_only=True) """ Rich console to display runtime errors. """ def __str__(self): if self.msg is not None: return self.msg strings = [] if self.verbose: strings.append(type(self).__name__) if self.target: file, lineno = _get_function_info(self.target) strings.append(f'Function defined in file "{file}", line {lineno}:') strings.append(f" {self.target.__name__}{cyclopts.utils.signature(self.target)}") if self.root_input_tokens is not None: strings.append(f"Root Input Tokens: {self.root_input_tokens}") else: pass if strings: return "\n".join(strings) + "\n" else: return "" @define(kw_only=True) class ValidationError(CycloptsError): """Validator function raised an exception.""" exception_message: str = "" """Parenting Assertion/Value/Type Error message.""" group: Optional[Group] = None """If a group validator caused the exception.""" value: Any = cyclopts.utils.UNSET """Converted value that failed validation.""" def __str__(self): message = "" if self.argument: value = self.argument.value if self.value is cyclopts.utils.UNSET else self.value token = self.argument.tokens[0] provided_by = "" if not token.source or token.source == "cli" else f' provided by "{token.source}"' name = token.keyword if token.keyword else self.argument.name.lstrip("-").upper() message = f'Invalid value "{value}" for "{name}"{provided_by}.' elif self.group: if self.group.name: message = f'Invalid values for group "{self.group.name}".' elif self.command_chain: message = f"Invalid values for command {self.command_chain[-1]!r}." else: raise NotImplementedError if self.exception_message: return f"{super().__str__()}{message} {self.exception_message}" else: return f"{super().__str__()}{message}" @define(kw_only=True) class UnknownOptionError(CycloptsError): """Unknown/unregistered option provided by the cli. A nearest-neighbor parameter suggestion may be printed. """ token: Token """Token without a matching parameter.""" argument_collection: "ArgumentCollection" """Argument collection of plausible options.""" def __str__(self): value = self.token.keyword or self.token.value if self.token.source == "cli": response = f'Unknown option: "{value}".' else: response = f'Unknown option: "{value}" from "{self.token.source}".' if keyword := self.token.keyword or self.token.value: import difflib candidates = list(chain.from_iterable(x.names for x in self.argument_collection)) close_matches = difflib.get_close_matches(keyword, candidates, n=1, cutoff=0.6) if close_matches: response += f' Did you mean "{close_matches[0]}"?' return super().__str__() + response @define(kw_only=True) class CoercionError(CycloptsError): """There was an error performing automatic type coercion.""" token: Optional["Token"] = None """ Input token that couldn't be coerced. """ target_type: Optional[type] = None """ Intended type to coerce into. """ def __str__(self): if self.msg is not None: if not self.token or self.token.keyword is None: return self.msg else: return f"Invalid value for {self.token.keyword}: {self.msg}" else: import json # If a JsonDecodeError, try and verbosify it. if isinstance(self.__cause__, json.JSONDecodeError): msg = json_decode_error_verbosifier(self.__cause__) # pyright: ignore[reportArgumentType] if not self.token or self.token.keyword is None: return msg else: return f"Invalid value for {self.token.keyword}: {msg}" assert self.argument is not None assert self.target_type is not None msg = super().__str__() if get_origin(self.target_type) is Literal: choices = "{" + ", ".join(repr(x) for x in get_args(self.target_type)) + "}" target_type_name = f"one of {choices}" else: target_type_name = get_hint_name(self.target_type) if not self.token: msg += f'Invalid value for "{self.argument.name}": unable to convert value to {target_type_name}.' elif self.token.keyword is None: positional_name = self.argument.name.lstrip("-").upper() if self.token.source == "" or self.token.source == "cli": msg += f'Invalid value for "{positional_name}": unable to convert "{self.token.value}" into {target_type_name}.' else: msg += f'Invalid value for "{positional_name}" from {self.token.source}: unable to convert "{self.token.value}" into {target_type_name}.' else: if self.token.source == "" or self.token.source == "cli": msg += f'Invalid value for "{self.token.keyword}": unable to convert "{self.token.value}" into {target_type_name}.' else: msg += f'Invalid value for "{self.token.keyword}" from {self.token.source}: unable to convert "{self.token.value}" into {target_type_name}.' return msg class InvalidCommandError(CycloptsError): """CLI token combination did not yield a valid command.""" def __str__(self): assert self.unused_tokens token = self.unused_tokens[0] response = f'Unknown command "{token}".' if self.app and self.app._commands: import difflib close_matches = difflib.get_close_matches(token, self.app._commands, n=1, cutoff=0.6) if close_matches: response += f' Did you mean "{close_matches[0]}"?' return super().__str__() + response @define(kw_only=True) class UnusedCliTokensError(CycloptsError): """Not all CLI tokens were used as expected.""" def __str__(self): assert self.unused_tokens is not None return super().__str__() + f"Unused Tokens: {self.unused_tokens}." @define(kw_only=True) class MissingArgumentError(CycloptsError): """A required argument was not provided.""" tokens_so_far: list[str] = field(factory=list) """If the matched parameter requires multiple tokens, these are the ones we have parsed so far.""" def __str__(self): assert self.argument is not None strings = [] count, _ = self.argument.token_count() if count == 0: required_string = "flag required" only_got_string = "" elif count == 1: required_string = "requires an argument" only_got_string = "" else: required_string = f"requires {count} positional arguments" received_count = len(self.tokens_so_far) % count only_got_string = f" Only got {received_count}." if received_count else "" close_match_string = "" if self.unused_tokens and self.argument.field_info.is_keyword: import difflib candidates = [x for x in self.unused_tokens if is_option_like(x)] close_matches = difflib.get_close_matches(self.argument.name, candidates, n=1, cutoff=0.6) if close_matches: close_match_string = f'Did you mean "{self.argument.name}" instead of "{close_matches[0]}"?' if self.command_chain: strings.append( f'Command "{" ".join(self.command_chain)}" parameter "{self.argument.name}" {required_string}.{only_got_string}' ) else: strings.append(f'Parameter "{self.argument.name}" {required_string}.{only_got_string}') if close_match_string: strings.append(close_match_string) if self.verbose: strings.append(f" Parsed: {self.tokens_so_far}.") return super().__str__() + " ".join(strings) @define(kw_only=True) class RepeatArgumentError(CycloptsError): """The same parameter has erroneously been specified multiple times.""" token: "Token" """The repeated token.""" def __str__(self): return super().__str__() + f"Parameter {self.token.keyword} specified multiple times." @define(kw_only=True) class ArgumentOrderError(CycloptsError): """Cannot supply a POSITIONAL_OR_KEYWORD argument with a keyword, and then a later POSITIONAL_OR_KEYWORD argument positionally.""" token: str prior_positional_or_keyword_supplied_as_keyword_arguments: list["Argument"] def __str__(self): assert self.argument is not None plural = len(self.prior_positional_or_keyword_supplied_as_keyword_arguments) > 1 display_name = next((x.keyword for x in self.argument.tokens if x.keyword), self.argument.name).lstrip("-") prior_display_names = [ x.tokens[0].keyword for x in self.prior_positional_or_keyword_supplied_as_keyword_arguments ] if len(prior_display_names) == 1: prior_display_names = prior_display_names[0] return ( super().__str__() + f"Cannot specify token {self.token!r} positionally for parameter {display_name!r} due to previously specified keyword{'s' if plural else ''} {prior_display_names!r}. {prior_display_names!r} must either be passed positionally, or {self.token!r} must be passed as a keyword to {self.argument.name!r}." ) @define(kw_only=True) class MixedArgumentError(CycloptsError): """Cannot supply keywords and non-keywords to the same argument.""" def __str__(self): assert self.argument is not None display_name = next((x.keyword for x in self.argument.tokens if x.keyword), self.argument.name) return super().__str__() + f'Cannot supply keyword & non-keyword arguments to "{display_name}".' def format_cyclopts_error(e: Any): from rich import box from rich.panel import Panel from rich.text import Text panel = Panel( Text(str(e), "default"), title="Error", box=box.ROUNDED, expand=True, title_align="left", style="red", ) return panel cyclopts-3.9.0/cyclopts/field_info.py000066400000000000000000000172131475451620500177100ustar00rootroot00000000000000import inspect from typing import Annotated, Any, ClassVar, Optional, Sequence, get_args, get_origin # noqa: F401 import attrs from attrs import field import cyclopts.utils from cyclopts.annotations import ( NotRequired, Required, is_annotated, is_attrs, is_namedtuple, is_pydantic, is_typeddict, resolve, resolve_annotated, resolve_optional, ) POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD def _replace_annotated_type(src_type, dst_type): if not is_annotated(src_type): return dst_type return Annotated[(dst_type,) + get_args(src_type)[1:]] # pyright: ignore @attrs.define class FieldInfo: """Extension of :class:`inspect.Parameter`.""" names: tuple[str, ...] kind: inspect._ParameterKind required: bool = field(kw_only=True) default: Any = field(default=inspect.Parameter.empty, kw_only=True) annotation: Any = field(default=inspect.Parameter.empty, kw_only=True) ################### # Class Variables # ################### empty: ClassVar = inspect.Parameter.empty POSITIONAL_OR_KEYWORD: ClassVar = inspect.Parameter.POSITIONAL_OR_KEYWORD POSITIONAL_ONLY: ClassVar = inspect.Parameter.POSITIONAL_ONLY KEYWORD_ONLY: ClassVar = inspect.Parameter.KEYWORD_ONLY VAR_POSITIONAL: ClassVar = inspect.Parameter.VAR_POSITIONAL VAR_KEYWORD: ClassVar = inspect.Parameter.VAR_KEYWORD POSITIONAL: ClassVar[frozenset] = frozenset({POSITIONAL_OR_KEYWORD, POSITIONAL_ONLY, VAR_POSITIONAL}) KEYWORD: ClassVar[frozenset] = frozenset({POSITIONAL_OR_KEYWORD, KEYWORD_ONLY, VAR_KEYWORD}) @classmethod def from_iparam(cls, iparam: inspect.Parameter, *, required: Optional[bool] = None): if required is None: required = ( iparam.default is iparam.empty and iparam.kind != iparam.VAR_KEYWORD and iparam.kind != iparam.VAR_POSITIONAL ) return cls( names=(iparam.name,), annotation=iparam.annotation, kind=iparam.kind, default=iparam.default, required=required, ) @property def hint(self): """Annotation with Optional-removed and cyclopts type-inferring.""" hint = self.annotation if hint is inspect.Parameter.empty or resolve(hint) is Any: hint = _replace_annotated_type( hint, str if self.default is inspect.Parameter.empty or self.default is None else type(self.default) ) hint = resolve_optional(hint) return hint @property def name(self): """The **first** provided name.""" return self.names[0] @property def is_positional(self) -> bool: return self.kind in self.POSITIONAL @property def is_positional_only(self) -> bool: return self.kind in (POSITIONAL_ONLY, VAR_POSITIONAL) @property def is_keyword(self) -> bool: return self.kind in self.KEYWORD @property def is_keyword_only(self) -> bool: return self.kind in (KEYWORD_ONLY, VAR_KEYWORD) def _typed_dict_field_infos(typeddict) -> dict[str, FieldInfo]: # The ``__required_keys__`` and ``__optional_keys__`` attributes of TypedDict are kind of broken in dict[str, FieldInfo]: signature = cyclopts.utils.signature(f.__init__) out = {} for name, iparam in signature.parameters.items(): if iparam.name == "self": continue if not include_var_positional and iparam.kind is iparam.VAR_POSITIONAL: continue if not include_var_keyword and iparam.kind is iparam.VAR_KEYWORD: continue out[name] = FieldInfo.from_iparam(iparam) return out def _pydantic_field_infos(model) -> dict[str, FieldInfo]: from pydantic_core import PydanticUndefined out = {} for python_name, pydantic_field in model.model_fields.items(): names = [] if pydantic_field.alias: if model.model_config.get("populate_by_name", False): names.append(python_name) names.append(pydantic_field.alias) else: names.append(python_name) # Pydantic places ``Annotated`` data into pydantic.FieldInfo.metadata, while # pydantic.FieldInfo.annotation contains the "real" resolved type-hint. # We have to re-combine them into a single Annotated hint. if pydantic_field.metadata: annotation = Annotated[(pydantic_field.annotation,) + tuple(pydantic_field.metadata)] # pyright: ignore else: annotation = pydantic_field.annotation out[python_name] = FieldInfo( names=tuple(names), kind=inspect.Parameter.KEYWORD_ONLY if pydantic_field.kw_only else inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=annotation, default=FieldInfo.empty if pydantic_field.default is PydanticUndefined else pydantic_field.default, required=pydantic_field.is_required(), ) return out def _namedtuple_field_infos(hint) -> dict[str, FieldInfo]: out = {} for name in hint._fields: out[name] = FieldInfo( names=(name,), kind=FieldInfo.POSITIONAL_OR_KEYWORD, annotation=hint.__annotations__.get(name, str), default=hint._field_defaults.get(name, FieldInfo.empty), required=name not in hint._field_defaults, ) return out def _attrs_field_infos(hint) -> dict[str, FieldInfo]: out = {} signature = cyclopts.utils.signature(hint.__init__) iparams = signature.parameters for attribute in hint.__attrs_attrs__: if not attribute.init: continue iparam = iparams[attribute.alias] if isinstance(attribute.default, attrs.Factory): # pyright: ignore required = False default = None # Not strictly True, but we don't want to invoke factory elif attribute.default is attrs.NOTHING: required = True default = FieldInfo.empty else: required = False default = attribute.default out[iparam.name] = FieldInfo( names=(attribute.alias,), annotation=attribute.type, kind=iparam.kind, default=default, required=required, ) return out def get_field_infos( hint, *, include_var_positional=False, include_var_keyword=False, ) -> dict[str, FieldInfo]: if is_pydantic(hint): return _pydantic_field_infos(hint) elif is_namedtuple(hint): return _namedtuple_field_infos(hint) elif is_typeddict(hint): return _typed_dict_field_infos(hint) elif is_attrs(hint): return _attrs_field_infos(hint) else: return _generic_class_field_infos(hint) cyclopts-3.9.0/cyclopts/group.py000066400000000000000000000110011475451620500167330ustar00rootroot00000000000000import itertools from collections.abc import Iterable from typing import ( TYPE_CHECKING, Any, Callable, Optional, Union, cast, ) from attrs import field from cyclopts.utils import UNSET, SortHelper, frozen, is_iterable, resolve_callables, to_tuple_converter if TYPE_CHECKING: from cyclopts.argument import ArgumentCollection from cyclopts.parameter import Parameter def _group_default_parameter_must_be_none(instance, attribute, value: Optional["Parameter"]): if value is None: return if value.group: raise ValueError("Group default_parameter cannot have a group.") # Used for Group.sorted _sort_key_counter = itertools.count() @frozen class Group: name: str = "" help: str = "" # All below parameters are keyword-only _show: Optional[bool] = field(default=None, alias="show", kw_only=True) _sort_key: Any = field( default=None, alias="sort_key", converter=lambda x: UNSET if x is None else x, kw_only=True, ) # This can ONLY ever be a Tuple[Callable, ...] validator: Union[None, Callable[["ArgumentCollection"], Any], Iterable[Callable[["ArgumentCollection"], Any]]] = ( field( default=None, converter=lambda x: cast(tuple[Callable, ...], to_tuple_converter(x)), kw_only=True, ) ) default_parameter: Optional["Parameter"] = field( default=None, validator=_group_default_parameter_must_be_none, kw_only=True, ) @property def show(self): return bool(self.name) if self._show is None else self._show @property def sort_key(self): return None if self._sort_key is UNSET else self._sort_key @classmethod def create_default_arguments(cls): return cls("Arguments") @classmethod def create_default_parameters(cls): return cls("Parameters") @classmethod def create_default_commands(cls): return cls("Commands") @classmethod def create_ordered(cls, name="", help="", *, show=None, sort_key=None, validator=None, default_parameter=None): """Create a group with a globally incrementing :attr:`~Group.sort_key`. Used to create a group that will be displayed **after** a previously instantiated :meth:`Group.create_ordered` group on the help-page. Parameters ---------- name: str Group name used for the help-page and for group-referenced-by-string. This is a title, so the first character should be capitalized. If a name is not specified, it will not be shown on the help-page. help: str Additional documentation shown on the help-page. This will be displayed inside the group's panel, above the parameters/commands. show: Optional[bool] Show this group on the help-page. Defaults to :obj:`None`, which will only show the group if a ``name`` is provided. sort_key: Any If provided, **prepended** to the globally incremented counter value (i.e. has priority during sorting). validator: Union[None, Callable[["ArgumentCollection"], Any], Iterable[Callable[["ArgumentCollection"], Any]]] Group validator to collectively apply. default_parameter: Optional[cyclopts.Parameter] Default parameter for elements within the group. """ count = next(_sort_key_counter) if sort_key is None: sort_key = (UNSET, count) elif is_iterable(sort_key): sort_key = (tuple(sort_key), count) else: sort_key = (sort_key, count) return cls( name, help, show=show, sort_key=sort_key, validator=validator, default_parameter=default_parameter, ) def sort_groups(groups: list[Group], attributes: list[Any]) -> tuple[list[Group], list[Any]]: """Sort groups for the help-page. Note, much logic is similar to here and ``HelpPanel.sort``, so any changes here should probably be reflected over there as well. """ assert len(groups) == len(attributes) if not groups: return groups, attributes sorted_entries = SortHelper.sort( [ SortHelper(resolve_callables(group._sort_key, group), group.name, (group, attribute)) for group, attribute in zip(groups, attributes) ] ) out_groups, out_attributes = zip(*[x.value for x in sorted_entries]) return list(out_groups), list(out_attributes) cyclopts-3.9.0/cyclopts/group_extractors.py000066400000000000000000000055031475451620500212230ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Union from cyclopts.group import Group if TYPE_CHECKING: from cyclopts.core import App def _create_or_append( group_mapping: list[tuple[Group, list[Any]]], group: Union[str, Group], element: Any, ): # updates group_mapping inplace. if isinstance(group, str): group = Group(group) elif isinstance(group, Group): pass else: raise TypeError for mapping in group_mapping: if mapping[0].name == group.name: mapping[1].append(element) break else: group_mapping.append((group, [element])) def groups_from_app(app: "App") -> list[tuple[Group, list["App"]]]: """Extract Group/App association from all commands of ``app``. Returns ------- list List of items where each item is a tuple containing: * :class:`.Group` - The group * ``list[App]`` - The list of app subcommands within the group. """ assert not isinstance(app.group_commands, str) group_commands = app.group_commands or Group.create_default_commands() group_mapping: list[tuple[Group, list[App]]] = [ (group_commands, []), ] subapps = list(app.subapps) # 2 iterations need to be performed: # 1. Extract out all Group objects as they may have additional configuration. # 2. Assign/Create Groups out of the strings, as necessary. for subapp in subapps: assert isinstance(subapp.group, tuple) for group in subapp.group: if isinstance(group, Group): for mapping in group_mapping: if mapping[0] is group: break elif mapping[0].name == group.name: raise ValueError(f'Command Group "{group.name}" already exists.') else: group_mapping.append((group, [])) for subapp in subapps: if subapp.group: assert isinstance(subapp.group, tuple) for group in subapp.group: _create_or_append(group_mapping, group, subapp) else: _create_or_append(group_mapping, app.group_commands or Group.create_default_commands(), subapp) # Remove the empty groups group_mapping = [x for x in group_mapping if x[1]] # Sort alphabetically by name group_mapping.sort(key=lambda x: x[0].name) return group_mapping def inverse_groups_from_app(input_app: "App") -> list[tuple["App", list[Group]]]: out = [] seen_apps = [] for group, apps in groups_from_app(input_app): for app in apps: try: index = seen_apps.index(app) except ValueError: index = len(out) out.append((app, [])) seen_apps.append(app) out[index][1].append(group) return out cyclopts-3.9.0/cyclopts/help.py000066400000000000000000000351451475451620500165460ustar00rootroot00000000000000import inspect import sys from collections.abc import Iterable from enum import Enum from functools import lru_cache, partial from inspect import isclass from math import ceil from typing import ( TYPE_CHECKING, Any, Callable, Literal, Union, get_args, get_origin, ) from attrs import define, field import cyclopts.utils from cyclopts._convert import ITERABLE_TYPES from cyclopts.annotations import is_union from cyclopts.group import Group from cyclopts.utils import SortHelper, frozen, resolve_callables if TYPE_CHECKING: from rich.console import Console, ConsoleOptions, RenderableType, RenderResult from cyclopts.argument import ArgumentCollection from cyclopts.core import App if sys.version_info >= (3, 12): # pragma: no cover from typing import TypeAliasType else: # pragma: no cover TypeAliasType = None @lru_cache(maxsize=16) def docstring_parse(doc: str): """Addon to :func:`docstring_parser.parse` that double checks the `short_description`.""" import docstring_parser res = docstring_parser.parse(doc) cleaned_doc = inspect.cleandoc(doc) short = cleaned_doc.split("\n\n")[0] if res.short_description != short: if res.long_description is None: res.long_description = res.short_description elif res.short_description is not None: res.long_description = res.short_description + "\n" + res.long_description res.short_description = None return res @frozen class HelpEntry: name: str short: str description: "RenderableType" required: bool = False sort_key: Any = None def _text_factory(): from rich.text import Text return Text() @define class HelpPanel: format: Literal["command", "parameter"] title: str description: "RenderableType" = field(factory=_text_factory) entries: list[HelpEntry] = field(factory=list) def remove_duplicates(self): seen, out = set(), [] for item in self.entries: hashable = (item.name, item.short) if hashable not in seen: seen.add(hashable) out.append(item) self.entries = out def sort(self): """Sort entries in-place. Callable sort_keys are provided with no argument? """ if not self.entries: return if self.format == "command": sorted_sort_helper = SortHelper.sort( [SortHelper(entry.sort_key, (entry.name.startswith("-"), entry.name), entry) for entry in self.entries] ) self.entries = [x.value for x in sorted_sort_helper] else: raise NotImplementedError def __rich_console__(self, console: "Console", options: "ConsoleOptions") -> "RenderResult": if not self.entries: return _silent import textwrap from rich.box import ROUNDED from rich.console import Group as RichGroup from rich.console import NewLine from rich.panel import Panel from rich.table import Table from rich.text import Text wrap = partial( textwrap.wrap, subsequent_indent=" ", break_on_hyphens=False, tabsize=4, ) # (top, right, bottom, left) table = Table.grid(padding=(0, 2, 0, 0), pad_edge=False) panel_description = self.description if isinstance(panel_description, Text): panel_description.end = "" if panel_description.plain: panel_description = RichGroup(panel_description, NewLine(2)) else: # Should be either a RST or Markdown object if panel_description.markup: # pyright: ignore[reportAttributeAccessIssue] panel_description = RichGroup(panel_description, NewLine(1)) panel = Panel( RichGroup(panel_description, table), box=ROUNDED, expand=True, title_align="left", title=self.title, ) if self.format == "command": commands_width = ceil(console.width * 0.35) table.add_column("Commands", justify="left", max_width=commands_width, style="cyan") table.add_column("Description", justify="left") for entry in self.entries: name = entry.name if entry.short: name += " " + entry.short name = "\n".join(wrap(name, commands_width)) table.add_row(name, entry.description) elif self.format == "parameter": options_width = ceil(console.width * 0.35) short_width = ceil(console.width * 0.1) has_short = any(entry.short for entry in self.entries) has_required = any(entry.required for entry in self.entries) if has_required: table.add_column("Asterisk", justify="left", width=1, style="red bold") table.add_column("Options", justify="left", overflow="fold", max_width=options_width, style="cyan") if has_short: table.add_column("Short", justify="left", overflow="fold", max_width=short_width, style="green") table.add_column("Description", justify="left", overflow="fold") lookup = {col.header: (i, col.max_width) for i, col in enumerate(table.columns)} for entry in self.entries: row = [""] * len(table.columns) def add(key, value, custom_wrap=False): try: index, max_width = lookup[key] except KeyError: return if custom_wrap and max_width: value = "\n".join(wrap(value, max_width)) row[index] = value # noqa: B023 add("Asterisk", "*" if entry.required else "") add("Options", entry.name, custom_wrap=True) add("Short", entry.short) add("Description", entry.description) table.add_row(*row) else: raise NotImplementedError yield panel class SilentRich: """Dummy object that causes nothing to be printed.""" def __rich_console__(self, console: "Console", options: "ConsoleOptions") -> "RenderResult": # This generator yields nothing, so ``rich`` will print nothing for this object. if False: yield _silent = SilentRich() def _is_short(s): return not s.startswith("--") and s.startswith("-") def format_usage( app, command_chain: Iterable[str], ): from rich.text import Text usage = [] usage.append("Usage:") usage.append(app.name[0]) usage.extend(command_chain) for command in command_chain: app = app[command] if any(x.show for x in app.subapps): usage.append("COMMAND") if app.default_command: to_show = set() for parameter in cyclopts.utils.signature(app.default_command).parameters.values(): if parameter.kind in (parameter.POSITIONAL_ONLY, parameter.VAR_POSITIONAL, parameter.POSITIONAL_OR_KEYWORD): to_show.add("[ARGS]") if parameter.kind in (parameter.KEYWORD_ONLY, parameter.VAR_KEYWORD, parameter.POSITIONAL_OR_KEYWORD): to_show.add("[OPTIONS]") usage.extend(sorted(to_show)) return Text(" ".join(usage) + "\n", style="bold") def format_doc(app: "App", format: str = "restructuredtext"): from rich.console import Group as RichGroup from rich.console import NewLine raw_doc_string = app.help if not raw_doc_string: return _silent parsed = docstring_parse(raw_doc_string) components: list[Union[str, tuple[str, str]]] = [] if parsed.short_description: components.append(parsed.short_description + "\n") if parsed.long_description: if parsed.short_description: components.append("\n") components.append(parsed.long_description + "\n") return RichGroup(format_str(*components, format=format), NewLine()) def format_str(*components: Union[str, tuple[str, str]], format: str) -> "RenderableType": """Format the sequence of components according to format. Parameters ---------- components: str | tuple[str, str] Either a plain string, or a tuple of string and formatting style. If formatting style is provided, the string-to-be-displayed WILL be escaped. """ format = format.lower() if format == "plaintext": from rich.text import Text aggregate = [] for component in components: if isinstance(component, str): aggregate.append(component) else: aggregate.append(component[0]) return Text.assemble("".join(aggregate).rstrip()) elif format in ("markdown", "md"): from rich.markdown import Markdown aggregate = [] for component in components: if isinstance(component, str): aggregate.append(component) else: # Ignore style for now :( aggregate.append(component[0]) return Markdown("".join(aggregate)) elif format in ("restructuredtext", "rst"): from rich_rst import RestructuredText aggregate = [] for component in components: if isinstance(component, str): aggregate.append(component) else: # Ignore style for now :( aggregate.append(component[0]) return RestructuredText("".join(aggregate)) elif format == "rich": from rich.text import Text def walk_components(): for component in components: if isinstance(component, str): yield Text.from_markup(component.rstrip()) else: yield Text(component[0].rstrip(), style=component[1]) text = Text() for component in walk_components(): text.append(component) return text else: raise ValueError(f'Unknown help_format "{format}"') def _get_choices(type_: type, name_transform: Callable[[str], str]) -> str: get_choices = partial(_get_choices, name_transform=name_transform) choices: str = "" _origin = get_origin(type_) if isclass(type_) and issubclass(type_, Enum): choices = ", ".join(name_transform(x.name) for x in type_) elif is_union(_origin): inner_choices = [get_choices(inner) for inner in get_args(type_)] choices = ", ".join(x for x in inner_choices if x) elif _origin is Literal: choices = ", ".join(str(x) for x in get_args(type_)) elif _origin in ITERABLE_TYPES: args = get_args(type_) if len(args) == 1 or (_origin is tuple and len(args) == 2 and args[1] is Ellipsis): choices = get_choices(args[0]) elif TypeAliasType is not None and isinstance(type_, TypeAliasType): choices = get_choices(type_.__value__) return choices def create_parameter_help_panel( group: "Group", argument_collection: "ArgumentCollection", format: str, ) -> HelpPanel: help_panel = HelpPanel(format="parameter", title=group.name, description=format_str(group.help, format=format)) def help_append(text, style=""): if help_components: text = " " + text if style: help_components.append((text, style)) else: help_components.append(text) entries_positional, entries_kw = [], [] for argument in argument_collection.filter_by(show=True): assert argument.parameter.name_transform help_components = [] options = list(argument.names) # Add an all-uppercase name if it's an argument if argument.index is not None: arg_name = options[0].lstrip("-").upper() if arg_name != options[0]: options = [arg_name, *options] short_options, long_options = [], [] for option in options: if _is_short(option): short_options.append(option) else: long_options.append(option) if argument.parameter.help: help_append(argument.parameter.help) if argument.parameter.show_choices: choices = _get_choices(argument.hint, argument.parameter.name_transform) if choices: help_append(rf"[choices: {choices}]", "dim") if argument.parameter.show_env_var and argument.parameter.env_var: env_vars = ", ".join(argument.parameter.env_var) help_append(rf"[env var: {env_vars}]", "dim") if argument.show_default: default = "" if isclass(argument.hint) and issubclass(argument.hint, Enum): default = argument.parameter.name_transform(argument.field_info.default.name) else: default = argument.field_info.default help_append(rf"[default: {default}]", "dim") if argument.required: help_append(r"[required]", "dim red") # populate row entry = HelpEntry( name=" ".join(long_options), description=format_str(*help_components, format=format), short=" ".join(short_options), required=argument.required, ) if argument.field_info.is_positional: entries_positional.append(entry) else: entries_kw.append(entry) help_panel.entries.extend(entries_positional) help_panel.entries.extend(entries_kw) return help_panel def format_command_entries(apps: Iterable["App"], format: str) -> list: entries = [] for app in apps: if not app.show: continue short_names, long_names = [], [] for name in app.name: short_names.append(name) if _is_short(name) else long_names.append(name) entry = HelpEntry( name="\n".join(long_names), short=" ".join(short_names), description=format_str(docstring_parse(app.help).short_description or "", format=format), sort_key=resolve_callables(app.sort_key, app), ) if entry not in entries: entries.append(entry) return entries def resolve_help_format(app_chain: Iterable["App"]) -> str: # Resolve help_format; None fallsback to parent; non-None overwrites parent. format_ = "restructuredtext" for app in app_chain: if app.help_format is not None: format_ = app.help_format return format_ def resolve_version_format(app_chain: Iterable["App"]) -> str: format_ = resolve_help_format(app_chain) for app in app_chain: if app.version_format is not None: format_ = app.version_format return format_ cyclopts-3.9.0/cyclopts/parameter.py000066400000000000000000000276231475451620500176000ustar00rootroot00000000000000import inspect from collections.abc import Iterable from copy import deepcopy from functools import partial from typing import ( Any, Callable, List, Optional, Sequence, Tuple, TypeVar, Union, cast, get_args, get_origin, ) import attrs from attrs import define, field import cyclopts._env_var import cyclopts.utils from cyclopts._convert import ITERABLE_TYPES, convert from cyclopts.annotations import is_annotated, is_union, resolve_optional from cyclopts.group import Group from cyclopts.utils import ( default_name_transform, frozen, optional_to_tuple_converter, record_init, to_tuple_converter, ) ITERATIVE_BOOL_IMPLICIT_VALUE = frozenset( { Iterable[bool], Sequence[bool], List[bool], list[bool], Tuple[bool, ...], tuple[bool, ...], } ) T = TypeVar("T") _NEGATIVE_FLAG_TYPES = frozenset([bool, *ITERABLE_TYPES, *ITERATIVE_BOOL_IMPLICIT_VALUE]) def _not_hyphen_validator(instance, attribute, values): for value in values: if value is not None and value.startswith("-"): raise ValueError(f'{attribute.alias} value must NOT start with "-".') def _negative_converter(default: tuple[str, ...]): def converter(value) -> tuple[str, ...]: if value is None: return default else: return to_tuple_converter(value) return converter # TODO: Breaking change; all fields after ``name`` should be ``kw_only=True``. @record_init("_provided_args") @frozen class Parameter: """Cyclopts configuration for individual function parameters with :obj:`~typing.Annotated`. Example usage: .. code-block:: python from cyclopts import app, Parameter from typing import Annotated app = App() @app.default def main(foo: Annotated[int, Parameter(name="bar")]): print(foo) app() .. code-block:: console $ my-script 100 100 $ my-script --bar 100 100 """ # All attribute docstrings has been moved to ``docs/api.rst`` for greater control with attrs. # This can ONLY ever be a Tuple[str, ...] # Usually starts with "--" or "-" name: Union[None, str, Iterable[str]] = field( default=None, converter=lambda x: cast(tuple[str, ...], to_tuple_converter(x)), ) _converter: Callable = field(default=None, alias="converter") # This can ONLY ever be a Tuple[Callable, ...] validator: Union[None, Callable, Iterable[Callable]] = field( default=(), converter=lambda x: cast(tuple[Callable, ...], to_tuple_converter(x)), ) # This can ONLY ever be ``None`` or ``Tuple[str, ...]`` negative: Union[None, str, Iterable[str]] = field(default=None, converter=optional_to_tuple_converter) # This can ONLY ever be a Tuple[Union[Group, str], ...] group: Union[None, Group, str, Iterable[Union[Group, str]]] = field( default=None, converter=to_tuple_converter, hash=False ) parse: bool = field(default=None, converter=attrs.converters.default_if_none(True)) _show: Optional[bool] = field(default=None, alias="show") show_default: Optional[bool] = field(default=None) show_choices: bool = field(default=None, converter=attrs.converters.default_if_none(True)) help: Optional[str] = field(default=None) show_env_var: bool = field(default=None, converter=attrs.converters.default_if_none(True)) # This can ONLY ever be a Tuple[str, ...] env_var: Union[None, str, Iterable[str]] = field( default=None, converter=lambda x: cast(tuple[str, ...], to_tuple_converter(x)), ) env_var_split: Callable = cyclopts._env_var.env_var_split # This can ONLY ever be a Tuple[str, ...] negative_bool: Union[None, str, Iterable[str]] = field( default=None, converter=_negative_converter(("no-",)), validator=_not_hyphen_validator, ) # This can ONLY ever be a Tuple[str, ...] negative_iterable: Union[None, str, Iterable[str]] = field( default=None, converter=_negative_converter(("empty-",)), validator=_not_hyphen_validator, ) required: Optional[bool] = field(default=None) allow_leading_hyphen: bool = field(default=False) _name_transform: Optional[Callable[[str], str]] = field( alias="name_transform", default=None, kw_only=True, ) # Should not get inherited accepts_keys: Optional[bool] = field(default=None) # Should not get inherited consume_multiple: bool = field(default=None, converter=attrs.converters.default_if_none(False)) json_dict: Optional[bool] = field(default=None, kw_only=True) json_list: Optional[bool] = field(default=None, kw_only=True) # Populated by the record_attrs_init_args decorator. _provided_args: tuple[str] = field(factory=tuple, init=False, eq=False) @property def show(self) -> bool: return self._show if self._show is not None else self.parse @property def converter(self): return self._converter if self._converter else partial(convert, name_transform=self.name_transform) @property def name_transform(self): return self._name_transform if self._name_transform else default_name_transform def get_negatives(self, type_) -> tuple[str, ...]: if is_union(type_): type_ = next(x for x in get_args(type_) if x is not None) origin = get_origin(type_) if type_ not in _NEGATIVE_FLAG_TYPES: if origin: if origin not in _NEGATIVE_FLAG_TYPES: return () else: return () out, user_negatives = [], [] if self.negative: for negative in self.negative: (out if negative.startswith("-") else user_negatives).append(negative) if not user_negatives: return tuple(out) assert isinstance(self.name, tuple) for name in self.name: if not name.startswith("--"): # Only provide negation for option-like long flags. continue name = name[2:] name_components = name.split(".") if type_ is bool or type_ in ITERATIVE_BOOL_IMPLICIT_VALUE: negative_prefixes = self.negative_bool else: negative_prefixes = self.negative_iterable name_prefix = ".".join(name_components[:-1]) if name_prefix: name_prefix += "." assert isinstance(negative_prefixes, tuple) if self.negative is None: for negative_prefix in negative_prefixes: out.append(f"--{name_prefix}{negative_prefix}{name_components[-1]}") else: for negative in user_negatives: out.append(f"--{name_prefix}{negative}") return tuple(out) def __repr__(self): """Only shows non-default values.""" content = ", ".join( [ f"{a.alias}={getattr(self, a.name)!r}" for a in self.__attrs_attrs__ # pyright: ignore[reportAttributeAccessIssue] if a.alias in self._provided_args ] ) return f"{type(self).__name__}({content})" @classmethod def combine(cls, *parameters: Optional["Parameter"]) -> "Parameter": """Returns a new Parameter with combined values of all provided ``parameters``. Parameters ---------- `*parameters`: Optional[Parameter] Parameters who's attributes override ``self`` attributes. Ordered from least-to-highest attribute priority. """ kwargs = {} filtered = [x for x in parameters if x is not None] # In the common case of 0/1 parameters to combine, we can avoid # instantiating a new Parameter object. if len(filtered) == 1: return filtered[0] elif not filtered: return EMPTY_PARAMETER for parameter in filtered: for alias in parameter._provided_args: kwargs[alias] = getattr(parameter, _parameter_alias_to_name[alias]) return cls(**kwargs) @classmethod def default(cls) -> "Parameter": """Create a Parameter with all Cyclopts-default values. This is different than just :class:`Parameter` because the default values will be recorded and override all upstream parameter values. """ return cls( **{a.alias: a.default for a in cls.__attrs_attrs__ if a.init} # pyright: ignore[reportAttributeAccessIssue] ) @classmethod def from_annotation(cls, type_: Any, *default_parameters: Optional["Parameter"]) -> tuple[Any, "Parameter"]: """Resolve the immediate Parameter from a type hint.""" if type_ is inspect.Parameter.empty: if default_parameters: return type_, cls.combine(*default_parameters) else: return type_, EMPTY_PARAMETER else: type_, parameters = get_parameters(type_) return type_, cls.combine(*default_parameters, *parameters) def __call__(self, obj: T) -> T: """Decorator interface for annotating a function/class with a :class:`Parameter`. Most commonly used for directly configuring a class: .. code-block:: python @Parameter(...) class Foo: ... """ if not hasattr(obj, "__cyclopts__"): obj.__cyclopts__ = CycloptsConfig(obj=obj) # pyright: ignore[reportAttributeAccessIssue] elif obj.__cyclopts__.obj != obj: # pyright: ignore[reportAttributeAccessIssue] # Create a copy so that children class Parameter decorators don't impact the parent. obj.__cyclopts__ = deepcopy(obj.__cyclopts__) # pyright: ignore[reportAttributeAccessIssue] obj.__cyclopts__.parameters.append(self) # pyright: ignore[reportAttributeAccessIssue] return obj _parameter_alias_to_name = { p.alias: p.name for p in Parameter.__attrs_attrs__ # pyright: ignore[reportAttributeAccessIssue] if p.init } EMPTY_PARAMETER = Parameter() def validate_command(f: Callable): """Validate if a function abides by Cyclopts's rules. Raises ------ ValueError Function has naming or parameter/signature inconsistencies. """ if (f.__module__ or "").startswith("cyclopts"): # Speed optimization. return signature = cyclopts.utils.signature(f) for iparam in signature.parameters.values(): # Speed optimization: if an object is not annotated, then there's nothing # to validate. Checking if there's an annotation is significantly faster # than instantiating a cyclopts.Parameter object. if not is_annotated(iparam.annotation): continue _, cparam = Parameter.from_annotation(iparam.annotation) if not cparam.parse and iparam.kind is not iparam.KEYWORD_ONLY: raise ValueError("Parameter.parse=False must be used with a KEYWORD_ONLY function parameter.") def get_parameters(hint: T) -> tuple[T, list[Parameter]]: """At root level, checks for cyclopts.Parameter annotations. Includes checking the ``__cyclopts__`` attribute. Returns ------- hint Annotation hint with :obj:`Annotated` and :obj:`Optional` resolved. list[Parameter] List of parameters discovered. """ parameters = [] hint = resolve_optional(hint) if cyclopts_config := getattr(hint, "__cyclopts__", None): parameters.extend(cyclopts_config.parameters) if is_annotated(hint): inner = get_args(hint) hint = inner[0] parameters.extend(x for x in inner[1:] if isinstance(x, Parameter)) return hint, parameters @define class CycloptsConfig: """ Intended for storing additional data to a ``__cyclopts__`` attribute via decoration. """ obj: Any = None parameters: list[Parameter] = field(factory=list, init=False) cyclopts-3.9.0/cyclopts/protocols.py000066400000000000000000000003111475451620500176250ustar00rootroot00000000000000import inspect from typing import Any, Callable, Protocol class Dispatcher(Protocol): def __call__(self, command: Callable, bound: inspect.BoundArguments, ignored: dict[str, Any], /) -> Any: ... cyclopts-3.9.0/cyclopts/py.typed000066400000000000000000000000001475451620500167210ustar00rootroot00000000000000cyclopts-3.9.0/cyclopts/token.py000066400000000000000000000013731475451620500167320ustar00rootroot00000000000000from typing import Any, Optional from attrs import evolve, field from cyclopts.utils import UNSET, frozen @frozen(kw_only=True) class Token: """Tracks how a user supplied a value to the application.""" keyword: Optional[str] = None value: str = "" source: str = "" index: int = field(default=0, kw_only=True) keys: tuple[str, ...] = field(default=(), kw_only=True) implicit_value: Any = field(default=UNSET, kw_only=True) @property def address(self) -> tuple[tuple[str, ...], int]: """Hashable subkey destination address for this token.""" return (self.keys, self.index) def evolve(self, **kwargs) -> "Token": # TODO: replace return-hint with Self cp311 return evolve(self, **kwargs) cyclopts-3.9.0/cyclopts/types.py000066400000000000000000000205071475451620500167560ustar00rootroot00000000000000from pathlib import Path from typing import TYPE_CHECKING, Annotated, Any, Sequence from cyclopts import validators from cyclopts.parameter import Parameter if TYPE_CHECKING: from cyclopts.token import Token __all__ = [ # Path "ExistingPath", "ExistingFile", "ExistingDirectory", "Directory", "File", "ResolvedExistingPath", "ResolvedExistingFile", "ResolvedExistingDirectory", "ResolvedDirectory", "ResolvedFile", "ResolvedPath", # Path with extensions "BinPath", "ExistingBinPath", "CsvPath", "ExistingCsvPath", "ImagePath", "ExistingImagePath", "JsonPath", "ExistingJsonPath", "Mp4Path", "ExistingMp4Path", "TomlPath", "ExistingTomlPath", "TxtPath", "ExistingTxtPath", "YamlPath", "ExistingYamlPath", # Number "PositiveFloat", "NonNegativeFloat", "NegativeFloat", "NonPositiveFloat", "PositiveInt", "NonNegativeInt", "NegativeInt", "NonPositiveInt", "UInt8", "Int8", "UInt16", "Int16", "UInt32", "Int32", # Json, "Json", ] ######## # Path # ######## def _path_resolve_converter(type_, tokens: Sequence["Token"]): assert len(tokens) == 1 return type_(tokens[0].value).resolve() ExistingPath = Annotated[Path, Parameter(validator=validators.Path(exists=True))] "A :class:`~pathlib.Path` file or directory that **must** exist." ResolvedPath = Annotated[Path, Parameter(converter=_path_resolve_converter)] "A :class:`~pathlib.Path` file or directory. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path." ResolvedExistingPath = Annotated[ExistingPath, Parameter(converter=_path_resolve_converter)] "A :class:`~pathlib.Path` file or directory that **must** exist. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path." Directory = Annotated[Path, Parameter(validator=validators.Path(file_okay=False))] "A :class:`~pathlib.Path` that **must** be a directory (or not exist)." ExistingDirectory = Annotated[Path, Parameter(validator=validators.Path(exists=True, file_okay=False))] "A :class:`~pathlib.Path` directory that **must** exist." ResolvedDirectory = Annotated[Directory, Parameter(converter=_path_resolve_converter)] "A :class:`~pathlib.Path` directory. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path." ResolvedExistingDirectory = Annotated[ExistingDirectory, Parameter(converter=_path_resolve_converter)] "A :class:`~pathlib.Path` directory that **must** exist. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path." File = Annotated[Path, Parameter(validator=validators.Path(dir_okay=False))] "A :class:`~pathlib.File` that **must** be a file (or not exist)." ExistingFile = Annotated[Path, Parameter(validator=validators.Path(exists=True, dir_okay=False))] "A :class:`~pathlib.Path` file that **must** exist." ResolvedFile = Annotated[File, Parameter(converter=_path_resolve_converter)] "A :class:`~pathlib.Path` file. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path." ResolvedExistingFile = Annotated[ExistingFile, Parameter(converter=_path_resolve_converter)] "A :class:`~pathlib.Path` file that **must** exist. :meth:`~pathlib.Path.resolve` is invoked prior to returning the path." # Common path extensions BinPath = Annotated[Path, Parameter(validator=validators.Path(ext="bin", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``bin``." ExistingBinPath = Annotated[Path, Parameter(validator=validators.Path(ext="bin", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``bin``." CsvPath = Annotated[Path, Parameter(validator=validators.Path(ext="csv", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``csv``." ExistingCsvPath = Annotated[Path, Parameter(validator=validators.Path(ext="csv", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``csv``." TxtPath = Annotated[Path, Parameter(validator=validators.Path(ext="txt", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``txt``." ExistingTxtPath = Annotated[Path, Parameter(validator=validators.Path(ext="txt", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``txt``." ImagePath = Annotated[Path, Parameter(validator=validators.Path(ext=("png", "jpg", "jpeg"), dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension in {``png``, ``jpg``, ``jpeg``}." ExistingImagePath = Annotated[ Path, Parameter(validator=validators.Path(ext=("png", "jpg", "jpeg"), exists=True, dir_okay=False)) ] "A :class:`~pathlib.Path` that **must** exist and have extension in {``png``, ``jpg``, ``jpeg``}." Mp4Path = Annotated[Path, Parameter(validator=validators.Path(ext="mp4", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``mp4``." ExistingMp4Path = Annotated[Path, Parameter(validator=validators.Path(ext="mp4", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``mp4``." JsonPath = Annotated[Path, Parameter(validator=validators.Path(ext="json", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``json``." ExistingJsonPath = Annotated[Path, Parameter(validator=validators.Path(ext="json", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``json``." TomlPath = Annotated[Path, Parameter(validator=validators.Path(ext="toml", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``toml``." ExistingTomlPath = Annotated[Path, Parameter(validator=validators.Path(ext="toml", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``toml``." YamlPath = Annotated[Path, Parameter(validator=validators.Path(ext="yaml", dir_okay=False))] "A :class:`~pathlib.Path` that **must** have extension ``yaml``." ExistingYamlPath = Annotated[Path, Parameter(validator=validators.Path(ext="yaml", exists=True, dir_okay=False))] "A :class:`~pathlib.Path` that **must** exist and have extension ``yaml``." ########## # Number # ########## # foo PositiveFloat = Annotated[float, Parameter(validator=validators.Number(gt=0))] "A float that **must** be ``>0``." NonNegativeFloat = Annotated[float, Parameter(validator=validators.Number(gte=0))] "A float that **must** be ``>=0``." NegativeFloat = Annotated[float, Parameter(validator=validators.Number(lt=0))] "A float that **must** be ``<0``." NonPositiveFloat = Annotated[float, Parameter(validator=validators.Number(lte=0))] "A float that **must** be ``<=0``." PositiveInt = Annotated[int, Parameter(validator=validators.Number(gt=0))] "An int that **must** be ``>0``." NonNegativeInt = Annotated[int, Parameter(validator=validators.Number(gte=0))] "An int that **must** be ``>=0``." NegativeInt = Annotated[int, Parameter(validator=validators.Number(lt=0))] "An int that **must** be ``<0``." NonPositiveInt = Annotated[int, Parameter(validator=validators.Number(lte=0))] "An int that **must** be ``<=0``." UInt8 = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=255))] "An unsigned 8-bit integer." Int8 = Annotated[int, Parameter(validator=validators.Number(gte=-128, lte=127))] "A signed 8-bit integer." UInt16 = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=65535))] "An unsigned 16-bit integer." Int16 = Annotated[int, Parameter(validator=validators.Number(gte=-32768, lte=32767))] "A signed 16-bit integer." UInt32 = Annotated[int, Parameter(validator=validators.Number(gte=0, lte=4294967295))] "An unsigned 32-bit integer." Int32 = Annotated[int, Parameter(validator=validators.Number(gte=-2147483648, lte=2147483647))] "A signed 32-bit integer." ######## # Json # ######## def _json_converter(type_, tokens: Sequence["Token"]): import json assert len(tokens) == 1 out = json.loads(tokens[0].value) return out Json = Annotated[Any, Parameter(converter=_json_converter)] """ Parse a json-string from the CLI. Note: Since Cyclopts v3.6.0, all dataclass-like classes now natively attempt to parse json-strings, so practical use-case of this annotation is limited. Usage example: .. code-block:: python from cyclopts import App, types app = App() @app.default def main(json: types.Json): print(json) app() .. code-block:: console $ my-script '{"foo": 1, "bar": 2}' {'foo': 1, 'bar': 2} """ cyclopts-3.9.0/cyclopts/utils.py000066400000000000000000000316561475451620500167610ustar00rootroot00000000000000"""To prevent circular dependencies, this module should never import anything else from Cyclopts.""" import functools import inspect import sys from collections.abc import Iterable, Iterator from contextlib import suppress from operator import itemgetter from typing import TYPE_CHECKING, Any, Literal, Optional, Sequence, Tuple, Union from attrs import field, frozen # fmt: off # https://threeofwands.com/attra-iv-zero-overhead-frozen-attrs-classes/ if TYPE_CHECKING: from json import JSONDecodeError from attrs import frozen else: from attrs import define frozen = functools.partial(define, unsafe_hash=True) if sys.version_info >= (3, 10): # pragma: no cover def signature(f: Any) -> inspect.Signature: return inspect.signature(f, eval_str=True) else: # pragma: no cover def signature(f: Any) -> inspect.Signature: return inspect.signature(f) # fmt: on if sys.version_info >= (3, 10): # pragma: no cover from sys import stdlib_module_names else: # pragma: no cover # Copied from python3.11 sys.stdlib_module_names stdlib_module_names = frozenset( { "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "colorsys", "compileall", "concurrent", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "curses", "dataclasses", "datetime", "dbm", "decimal", "difflib", "dis", "distutils", "doctest", "email", "encodings", "ensurepip", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "http", "idlelib", "imaplib", "imghdr", "imp", "importlib", "inspect", "io", "ipaddress", "itertools", "json", "keyword", "lib2to3", "linecache", "locale", "logging", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "netrc", "nis", "nntplib", "nt", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "ossaudiodev", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pyexpat", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "textwrap", "this", "threading", "time", "timeit", "tkinter", "token", "tokenize", "tomllib", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "types", "typing", "unicodedata", "unittest", "urllib", "uu", "uuid", "venv", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "xdrlib", "xml", "xmlrpc", "zipapp", "zipfile", "zipimport", "zlib", "zoneinfo", } ) class SentinelMeta(type): def __repr__(cls) -> str: return f"<{cls.__name__}>" def __bool__(cls) -> Literal[False]: return False class Sentinel(metaclass=SentinelMeta): def __new__(cls): raise ValueError("Sentinel objects are not intended to be instantiated. Subclass instead.") class UNSET(Sentinel): """Special sentinel value indicating that no data was provided. **Do not instantiate**.""" def record_init(target: str): """Class decorator that records init argument names as a tuple to ``target``.""" def decorator(cls): original_init = cls.__init__ function_signature = signature(original_init) param_names = tuple(name for name in function_signature.parameters if name != "self") @functools.wraps(original_init) def new_init(self, *args, **kwargs): original_init(self, *args, **kwargs) # Circumvent frozen protection. object.__setattr__(self, target, tuple(param_names[i] for i in range(len(args))) + tuple(kwargs)) cls.__init__ = new_init return cls return decorator def is_iterable(obj) -> bool: if isinstance(obj, (list, tuple, set, dict)): # Fast path for common types return True return not isinstance(obj, str) and isinstance(obj, Iterable) def to_tuple_converter(value: Union[None, Any, Iterable[Any]]) -> tuple[Any, ...]: """Convert a single element or an iterable of elements into a tuple. Intended to be used in an ``attrs.Field``. If :obj:`None` is provided, returns an empty tuple. If a single element is provided, returns a tuple containing just that element. If an iterable is provided, converts it into a tuple. Parameters ---------- value: Optional[Union[Any, Iterable[Any]]] An element, an iterable of elements, or None. Returns ------- Tuple[Any, ...]: A tuple containing the elements. """ if value is None: return () elif is_iterable(value): return tuple(value) else: return (value,) def to_list_converter(value: Union[None, Any, Iterable[Any]]) -> list[Any]: return list(to_tuple_converter(value)) def optional_to_tuple_converter(value: Union[None, Any, Iterable[Any]]) -> Optional[tuple[Any, ...]]: """Convert a string or Iterable or None into an Iterable or None. Intended to be used in an ``attrs.Field``. """ if value is None: return None if not value: return () return to_tuple_converter(value) def default_name_transform(s: str) -> str: """Converts a python identifier into a CLI token. Performs the following operations (in order): 1. Convert the string to all lowercase. 2. Replace ``_`` with ``-``. 3. Strip any leading/trailing ``-`` (also stripping ``_``, due to point 2). Intended to be used with :attr:`App.name_transform` and :attr:`Parameter.name_transform`. Parameters ---------- s: str Input python identifier string. Returns ------- str Transformed name. """ return s.lower().replace("_", "-").strip("-") def grouper(iterable: Sequence, n: int) -> Iterator[Tuple[Any, ...]]: """Collect data into non-overlapping fixed-length chunks or blocks. https://docs.python.org/3/library/itertools.html#itertools-recipes """ if len(iterable) % n: raise ValueError(f"{iterable!r} is not divisible by {n}.") iterators = [iter(iterable)] * n return zip(*iterators) def is_option_like(token: str) -> bool: """Checks if a token looks like an option. Namely, negative numbers are not options, but a token like ``--foo`` is. """ with suppress(ValueError): complex(token) if token.lower() == "-j": # ``complex("-j")`` is a valid imaginary number, but more than likely # the caller meant it as a short flag. # https://github.com/BrianPugh/cyclopts/issues/328 return True return False return token.startswith("-") def is_builtin(obj: Any) -> bool: return getattr(obj, "__module__", "").split(".")[0] in stdlib_module_names def resolve_callables(t, *args, **kwargs): """Recursively resolves callable elements in a tuple. Returns an object that "looks like" the input, but with all callable's invoked and replaced with their return values. Positional and keyword elements will be passed along to each invocation. """ if isinstance(t, type(Sentinel)): return t if callable(t): return t(*args, **kwargs) elif is_iterable(t): resolved = [] for element in t: if isinstance(element, type(Sentinel)): resolved.append(element) elif callable(element): resolved.append(element(*args, **kwargs)) elif is_iterable(element): resolved.append(resolve_callables(element, *args, **kwargs)) else: resolved.append(element) return tuple(resolved) else: return t @frozen class SortHelper: key: Any fallback_key: Any = field(converter=to_tuple_converter) value: Any @staticmethod def sort(entries: Sequence["SortHelper"]) -> list["SortHelper"]: user_sort_key = [] ordered_no_user_sort_key = [] no_user_sort_key = [] for entry in entries: if entry.key in (UNSET, None): no_user_sort_key.append((entry.fallback_key, entry)) elif is_iterable(entry.key) and entry.key[0] in (UNSET, None): ordered_no_user_sort_key.append((entry.key[1:] + entry.fallback_key, entry)) else: user_sort_key.append(((entry.key, entry.fallback_key), entry)) user_sort_key.sort(key=itemgetter(0)) ordered_no_user_sort_key.sort(key=itemgetter(0)) no_user_sort_key.sort(key=itemgetter(0)) combined = user_sort_key + ordered_no_user_sort_key + no_user_sort_key return [x[1] for x in combined] def json_decode_error_verbosifier(decode_error: "JSONDecodeError", context: int = 20) -> str: """Not intended to be a super robust implementation, but robust enough to be helpful. Parameters ---------- context: int Number of surrounding-character context """ lines = decode_error.doc.splitlines() line = lines[decode_error.lineno - 1] error_index = decode_error.colno - 1 # colno is 1-indexed start = error_index - context if start <= 0: start = 0 prefix_ellipsis = "" segment_error_index = error_index else: prefix_ellipsis = "... " segment_error_index = error_index - start end = error_index + context if end >= len(line): end = len(line) + 1 suffix_ellipsis = "" else: suffix_ellipsis = " ..." segment = line[start:end] carat_pointer = " " * (len(prefix_ellipsis) + segment_error_index) + "^" response = ( f"JSONDecodeError:\n {prefix_ellipsis}{segment}{suffix_ellipsis}\n {carat_pointer}\n{str(decode_error)}" ) return response cyclopts-3.9.0/cyclopts/validators/000077500000000000000000000000001475451620500174045ustar00rootroot00000000000000cyclopts-3.9.0/cyclopts/validators/__init__.py000066400000000000000000000003711475451620500215160ustar00rootroot00000000000000__all__ = [ "LimitedChoice", "MutuallyExclusive", "Number", "Path", ] from cyclopts.validators._group import LimitedChoice, MutuallyExclusive from cyclopts.validators._number import Number from cyclopts.validators._path import Path cyclopts-3.9.0/cyclopts/validators/_group.py000066400000000000000000000032761475451620500212610ustar00rootroot00000000000000from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from cyclopts.argument import ArgumentCollection class LimitedChoice: def __init__(self, min: int = 0, max: Optional[int] = None): """Group validator that limits the number of selections per group. Commonly used for enforcing mutually-exclusive parameters (default behavior). Parameters ---------- min: int The minimum (inclusive) number of CLI parameters allowed. max: Optional[int] The maximum (inclusive) number of CLI parameters allowed. Defaults to ``1`` if ``min==0``, ``min`` otherwise. """ self.min = min self.max = (self.min or 1) if max is None else max if self.max < self.min: raise ValueError("max must be >=min.") def __call__(self, argument_collection: "ArgumentCollection"): argument_collection = argument_collection.filter_by(value_set=True) n_arguments = len(argument_collection) if self.min <= n_arguments <= self.max: return # Happy path offenders = "{" + ", ".join(a.name for a in argument_collection) + "}" if self.min == 0 and self.max == 1: raise ValueError(f"Mutually exclusive arguments: {offenders}") else: raise ValueError( f"Received {n_arguments} arguments: {offenders}. Only [{self.min}, {self.max}] choices may be specified." ) class MutuallyExclusive(LimitedChoice): def __init__(self): """Alias for :class:`LimitedChoice` to make intentions more obvious. Only 1 argument in the group can be supplied a value. """ super().__init__() cyclopts-3.9.0/cyclopts/validators/_number.py000066400000000000000000000057351475451620500214170ustar00rootroot00000000000000from typing import Any, Optional, Sequence, Union from cyclopts.utils import frozen Numeric = Union[int, float] NumericSequence = Sequence[Union[Numeric, "NumericSequence"]] @frozen(kw_only=True) class Number: """Limit input number to a value range. Example Usage: .. code-block:: python from cyclopts import App, Parameter, validators from typing import Annotated app = App() @app.default def main(age: Annotated[int, Parameter(validator=validators.Number(gte=0, lte=150))]): print(f"You are {age} years old.") app() .. code-block:: console $ my-script 100 You are 100 years old. $ my-script -1 ╭─ Error ───────────────────────────────────────────────────────╮ │ Invalid value "-1" for "AGE". Must be >= 0. │ ╰───────────────────────────────────────────────────────────────╯ $ my-script 200 ╭─ Error ───────────────────────────────────────────────────────╮ │ Invalid value "200" for "AGE". Must be <= 150. │ ╰───────────────────────────────────────────────────────────────╯ """ lt: Optional[Numeric] = None """Input value must be **less than** this value.""" lte: Optional[Numeric] = None """Input value must be **less than or equal** this value.""" gt: Optional[Numeric] = None """Input value must be **greater than** this value.""" gte: Optional[Numeric] = None """Input value must be **greater than or equal** this value.""" modulo: Optional[Numeric] = None """Input value must be a multiple of this value.""" def __call__(self, type_: Any, value: Union[Numeric, NumericSequence]): if isinstance(value, Sequence): if isinstance(value, str): raise TypeError for v in value: self(type_, v) else: if self.lt is not None and value >= self.lt: raise ValueError(f"Must be < {self.lt}.") if self.lte is not None and value > self.lte: raise ValueError(f"Must be <= {self.lte}.") if self.gt is not None and value <= self.gt: raise ValueError(f"Must be > {self.gt}.") if self.gte is not None and value < self.gte: raise ValueError(f"Must be >= {self.gte}.") if self.modulo is not None and value % self.modulo: raise ValueError(f"Must be a multiple of {self.modulo}.") cyclopts-3.9.0/cyclopts/validators/_path.py000066400000000000000000000111221475451620500210460ustar00rootroot00000000000000import pathlib from typing import Any, Iterable, Sequence, Union from attrs import field from cyclopts.utils import frozen, to_tuple_converter def ext_converter(value: Union[None, Any, Iterable[Any]]) -> tuple[str, ...]: return tuple(e.lower().lstrip(".") for e in to_tuple_converter(value)) @frozen(kw_only=True) class Path: """Assertions on properties of :class:`pathlib.Path`. Example Usage: .. code-block:: python from cyclopts import App, Parameter, validators from pathlib import Path from typing import Annotated app = App() @app.default def main( # ``src`` must be a file that exists. src: Annotated[Path, Parameter(validator=validators.Path(exists=True, dir_okay=False))], # ``dst`` must be a path that does **not** exist. dst: Annotated[Path, Parameter(validator=validators.Path(dir_okay=False, file_okay=False))], ): "Copies src->dst." dst.write_bytes(src.read_bytes()) app() .. code-block:: console $ my-script foo.bin bar.bin # if foo.bin does not exist ╭─ Error ───────────────────────────────────────────────────────╮ │ Invalid value "foo.bin" for "SRC". "foo.bin" does not exist. │ ╰───────────────────────────────────────────────────────────────╯ $ my-script foo.bin bar.bin # if bar.bin exists ╭─ Error ───────────────────────────────────────────────────────╮ │ Invalid value "bar.bin" for "DST". "bar.bin" already exists. │ ╰───────────────────────────────────────────────────────────────╯ """ exists: bool = False """If :obj:`True`, specified path **must** exist. Defaults to :obj:`False`.""" file_okay: bool = True """ If path exists, check it's type: * If :obj:`True`, specified path may be an **existing** file. * If :obj:`False`, then **existing** files are not allowed. Defaults to :obj:`True`. """ dir_okay: bool = True """ If path exists, check it's type: * If :obj:`True`, specified path may be an **existing** directory. * If :obj:`False`, then **existing** directories are not allowed. Defaults to :obj:`True`. """ # Can only ever really be a tuple[str, ...] ext: Union[str, Sequence[str]] = field(default=None, converter=ext_converter) """ Supplied path must have this extension (case insensitive). May or may not include the ".". """ def __attrs_post_init__(self): if self.exists and not self.file_okay and not self.dir_okay: raise ValueError("(exists=True, file_okay=False, dir_okay=False) is an invalid configuration.") def __call__(self, type_: Any, path: Union[pathlib.Path, Sequence[pathlib.Path]]): if isinstance(path, Sequence): if isinstance(path, str): raise TypeError for p in path: self(type_, p) else: if not isinstance(path, pathlib.Path): raise TypeError if self.ext and path.suffix.lower().lstrip(".") not in self.ext: if len(self.ext) == 1: raise ValueError(f'"{path}" must have extension "{self.ext[0]}".') else: pretty_ext = "{" + ", ".join(f'"{x}"' for x in self.ext) + "}" raise ValueError(f'"{path}" does not match one of supported extensions {pretty_ext}.') if path.exists(): if not self.file_okay and path.is_file(): if self.dir_okay: raise ValueError(f'Only directory is allowed, but "{path}" is a file.') else: raise ValueError(f'"{path}" already exists.') if not self.dir_okay and path.is_dir(): if self.file_okay: raise ValueError(f'Only file is allowed, but "{path}" is a directory.') else: raise ValueError(f'"{path}" already exists.') elif self.exists: raise ValueError(f'"{path}" does not exist.') cyclopts-3.9.0/docs/000077500000000000000000000000001475451620500143245ustar00rootroot00000000000000cyclopts-3.9.0/docs/Makefile000066400000000000000000000012141475451620500157620ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -W SPHINXBUILD ?= poetry run sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) cyclopts-3.9.0/docs/make.bat000066400000000000000000000014441475451620500157340ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) if "%1" == "" goto help %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd cyclopts-3.9.0/docs/source/000077500000000000000000000000001475451620500156245ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/Installation.rst000066400000000000000000000007671475451620500210310ustar00rootroot00000000000000.. _Detailed Installation: ============ Installation ============ Cyclopts requires Python >=3.9 and can be installed from PyPI via: .. code-block:: bash python -m pip install cyclopts To install directly from github, you can run: .. code-block:: bash python -m pip install git+https://github.com/BrianPugh/cyclopts.git For Cyclopts development, its recommended to use Poetry: .. code-block:: bash git clone https://github.com/BrianPugh/cyclopts.git cd cyclopts poetry install cyclopts-3.9.0/docs/source/_static/000077500000000000000000000000001475451620500172525ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/_static/.gitkeep000066400000000000000000000000001475451620500206710ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/_static/custom.css000066400000000000000000000013631475451620500213010ustar00rootroot00000000000000/* Have contents take up entire browser window width. */ .wy-nav-content { max-width: none !important } .wy-side-nav-search .version { color: #000000 !important; } .wy-side-nav-search>div.switch-menus>div.version-switch select { color: #000000; } /* override table width restrictions */ .wy-table-responsive table td, .wy-table-responsive table th { white-space: normal !important; } .wy-table-responsive { overflow: visible !important; } .custom-code-block { border: 1px solid #CCCCCC; background-color: #F9F9F9; padding: 10px; font-family: monospace; overflow: auto; } /* https://github.com/readthedocs/sphinx_rtd_theme/issues/1301#issuecomment-1876120817 */ .py.property { display: block !important; } cyclopts-3.9.0/docs/source/api.rst000066400000000000000000001340101475451620500171260ustar00rootroot00000000000000.. _API: === API === .. autoclass:: cyclopts.App :members: default, command, version_print, help_print, interactive_shell, parse_commands, parse_known_args, parse_args, assemble_argument_collection, update :special-members: __call__, __getitem__, __iter__ Cyclopts Application. .. attribute:: name :type: Optional[Union[str, Iterable[str]]] :value: None Name of application, or subcommand if registering to another application. Name resolution order: 1. User specified :attr:`~.App.name` parameter. 2. If a :attr:`~.App.default` function has been registered, the name of that function. 3. If the module name is ``__main__.py``, the name of the encompassing package. 4. The value of ``sys.argv[0]``; i.e. the name of the python script. Multiple names can be provided in the case of a subcommand, but this is relatively unusual. Example: .. code-block:: python from cyclopts import App app = App() app.command(App(name="foo")) @app["foo"].command def bar(): print("Running bar.") app() .. code-block:: console $ my-script foo bar Running bar. .. attribute:: help :type: Optional[str] :value: None Text to display on help screen. If not supplied, fallbacks to parsing the docstring of function registered with :meth:`.App.default`. .. code-block:: python from cyclopts import App app = App(help="This is my help string.") app() .. code-block:: $ my-script --help Usage: scratch.py COMMAND This is my help string. ╭─ Commands ────────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰───────────────────────────────────────────────────────────────────────╯ .. attribute:: help_flags :type: Union[str, Iterable[str]] :value: ("--help", "-h") CLI flags that trigger :meth:`help_print`. Set to an empty list to disable this feature. Defaults to ``["--help", "-h"]``. .. attribute:: help_format :type: Optional[Literal["plaintext", "markdown", "md", "restructuredtext", "rst"]] :value: None The markup language used in function docstring. If :obj:`None`, fallback to parenting :attr:`~.App.help_format`. If no :attr:`~.App.help_format` is defined, falls back to ``"restructuredtext"``. .. attribute:: help_on_error :type: Optional[bool] :value: None Prints the help-page before printing an error. If not set, attempts to inherit from parenting :class:`App`, eventually defaulting to :obj:`False`. .. attribute:: version_format :type: Optional[Literal["plaintext", "markdown", "md", "restructuredtext", "rst"]] :value: None The markup language used in the version string. If :obj:`None`, fallback to parenting :attr:`~.App.version_format`. If no :attr:`~.App.version_format` is defined, falls back to resolved :attr:`~.App.help_format`. .. attribute:: usage :type: Optional[str] :value: None Text to be displayed in lieue of the default ``Usage: app COMMAND ...`` at the beginning of the help-page. Set to an empty-string ``""`` to disable showing the default usage. .. attribute:: show :type: bool :value: True Show this **command** on the help screen. Hidden commands (``show=False``) are still executable. .. code-block:: python from cyclopts import App app = App() @app.command def foo(): print("Running foo.") @app.command(show=False) def bar(): print("Running bar.") app() .. code-block:: console $ my-script foo Running foo. $ my-script bar Running bar. $ my-script --help Usage: scratch.py COMMAND ╭─ Commands ─────────────────────────────────────────────────╮ │ foo │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────╯ .. attribute:: sort_key :type: Any :value: None Modifies command display order on the help-page. 1. If :attr:`sort_key`, or any of it's contents, are ``Callable``, then invoke it ``sort_key(app)`` and apply the returned value to (2) if :obj:`None`, (3) otherwise. 2. For all commands with ``sort_key==None`` (default value), sort them alphabetically. These sorted commands will be displayed **after** ``sort_key != None`` list (see 3). 3. For all commands with ``sort_key!=None``, sort them by ``(sort_key, app.name)``. It is the user's responsibility that ``sort_key`` s are comparable. Example usage: .. code-block:: python from cyclopts import App app = App() @app.command # sort_key not specified; will be sorted AFTER bob/charlie. def alice(): """Alice help description.""" @app.command(sort_key=2) def bob(): """Bob help description.""" @app.command(sort_key=1) def charlie(): """Charlie help description.""" app() Resulting help-page: .. code-block:: text Usage: demo.py COMMAND ╭─ Commands ──────────────────────────────────────────────────╮ │ charlie Charlie help description. │ │ bob Bob help description. │ │ alice Alice help description. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────╯ .. attribute:: version :type: Union[None, str, Callable] :value: None Version to be displayed when a :attr:`version_flags` is parsed. Defaults to the version of the package instantiating :class:`App`. If a :obj:`~typing.Callable`, it will be invoked with no arguments when version is queried. .. attribute:: version_flags :type: Union[str, Iterable[str]] :value: ("--version",) Token(s) that trigger :meth:`version_print`. Set to an empty list to disable version feature. Defaults to ``["--version"]``. .. attribute:: console :type: rich.console.Console :value: None Default :class:`rich.console.Console` to use when displaying runtime messages. Cyclopts console resolution is as follows: #. Any explicitly passed in console to methods like :meth:`App.__call__`, :meth:`App.parse_args`, etc. #. The relevant subcommand's :attr:`App.console` attribute, if not :obj:`None`. #. The parenting :attr:`App.console` (and so on), if not :obj:`None`. #. If all values are :obj:`None`, then the default :class:`~rich.console.Console` is used. .. attribute:: default_parameter :type: Parameter :value: None Default :class:`Parameter` configuration. Unspecified values of command-annotated :class:`Parameter` will inherit these values. See :ref:`Default Parameter` for more details. .. attribute:: group :type: Union[None, str, Group, Iterable[Union[str, Group]]] :value: None The group(s) that :attr:`default_command` belongs to. * If :obj:`None`, defaults to the ``"Commands"`` group. * If :obj:`str`, use an existing :class:`Group` (from neighboring sub-commands) with name, **or** create a :class:`Group` with provided name if it does not exist. * If :class:`Group`, directly use it. .. attribute:: group_commands :type: Group :value: Group("Commands") The default :class:`Group` that sub-commands are assigned to. .. attribute:: group_arguments :type: Group :value: Group("Arguments") The default :class:`Group` that positional-only parameters are assigned to. .. attribute:: group_parameters :type: Group :value: Group("Parameters") The default :class:`Group` that non-positional-only parameters are assigned to. .. attribute:: validator :type: Union[None, Callable, list[Callable]] :value: [] A function (or list of functions) where all the converted CLI-provided variables will be **keyword-unpacked**, regardless of their positional/keyword-type in the command function signature. The python variable names will be used, which may differ from their CLI names. Example usage: .. code-block:: python def validator(**kwargs): "Raise an exception if something is invalid." This validator runs **after** :class:`Parameter` and :class:`Group` validators. .. attribute:: name_transform :type: Optional[Callable[[str], str]] :value: None A function that converts function names to their CLI command counterparts. The function must have signature: .. code-block:: python def name_transform(s: str) -> str: ... The returned string should be **without** a leading ``--``. If :obj:`None` (default value), uses :func:`~.default_name_transform`. Subapps inherit from the first non-:obj:`None` parent :attr:`name_transform`. .. attribute:: config :type: Union[None, Callable, Iterable[Callable]] :value: None A function or list of functions that are consecutively executed after parsing CLI tokens and environment variables. These function(s) are called **before** any conversion and validation. Each config function must have signature: .. code-block:: python def config(apps: list["App"], commands: Tuple[str, ...], arguments: ArgumentCollection): """Modifies given mapping inplace with some injected values. Parameters ---------- apps: Tuple[App, ...] The application hierarchy that led to the current command function. The current command app is the last element of this tuple. commands: Tuple[str, ...] The CLI strings that led to the current command function. arguments: ArgumentCollection Complete ArgumentCollection for the app. Modify this collection inplace to influence values provided to the function. """ The intended use-case of this feature is to allow users to specify functions that can load defaults from some external configuration. See :ref:`cyclopts.config ` for useful builtins and :ref:`Config Files` for examples. .. attribute:: end_of_options_delimiter :type: Optional[str] :value: None All tokens after this delimiter will be force-interpreted as positional arguments. If no ``end_of_options_delimiter`` is set, it will default to POSIX-standard ``"--"``. Set to an empty string to disable. .. autoclass:: cyclopts.Parameter :special-members: __call__ .. attribute:: name :type: Union[None, str, Iterable[str]] :value: None Name(s) to expose to the CLI. If not specified, cyclopts will apply :attr:`name_transform` to the python parameter name. .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() @app.default def main(foo: Annotated[int, Parameter(name=("bar", "-b"))]): print(f"{foo=}") app() .. code-block:: console $ my-script --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────╮ │ * BAR --bar -b [required] │ ╰────────────────────────────────────────────────────────────────╯ $ my-script --bar 100 foo=100 $ my-script -b 100 foo=100 If specifying name in a nested data structure (e.g. a dataclass), beginning the name with a hyphen ``-`` will override any hierarchical dot-notation. .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass from typing import Annotated app = App() @dataclass class User: id: int # default behavior email: Annotated[str, Parameter(name="--email")] # overrides pwd: Annotated[str, Parameter(name="password")] # dot-notation with parent @app.command def create(user: User): print(f"Creating {user=}") app() .. code-block:: console $ my-script create --help Usage: scratch.py create [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────╮ │ * USER.ID --user.id [required] │ │ * EMAIL --email [required] │ │ * USER.PASSWORD [required] │ │ --user.password │ ╰────────────────────────────────────────────────────────────────╯ .. attribute:: converter :type: Optional[Callable] :value: None A function that converts tokens into an object. The converter should have signature: .. code-block:: python def converter(type_, tokens) -> Any: pass Where ``type_`` is the parameter's type hint, and ``tokens`` is either: * A ``list[cyclopts.Token]`` of CLI tokens (most commonly). .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() def converter(type_, tokens): assert type_ == tuple[int, int] return tuple(2 * int(x.value) for x in tokens) @app.default def main(coordinates: Annotated[tuple[int, int], Parameter(converter=converter)]): print(f"{coordinates=}") app() .. code-block:: console $ python my-script.py 7 12 coordinates=(14, 24) * A ``dict`` of :class:`Token` if keys are specified in the CLI. E.g. .. code-block:: console $ python my-script.py --foo.key1=val1 would be parsed into: .. code-block:: python tokens = { "key1": ["val1"], } If not provided, defaults to Cyclopts's internal coercion engine. .. attribute:: validator :type: Union[None, Callable, Iterable[Callable]] :value: None A function (or list of functions) that validates data returned by the :attr:`converter`. .. code-block:: python def validator(type_, value: Any) -> None: pass # Raise a TypeError, ValueError, or AssertionError here if data is invalid. .. attribute:: group :type: Union[None, str, Group, Iterable[Union[str, Group]]] :value: None The group(s) that this parameter belongs to. This can be used to better organize the help-page, and/or to add additional conversion/validation logic (such as ensuring mutually-exclusive arguments). If :obj:`None`, defaults to one of the following groups: 1. Parenting :attr:`.App.group_arguments` if the parameter is ``POSITIONAL_ONLY``. By default, this is ``Group("Arguments")``. 2. Parenting :attr:`.App.group_parameters` otherwise. By default, this is ``Group("Parameters")``. See :ref:`Groups` for examples. .. attribute:: negative :type: Union[None, str, Iterable[str]] :value: None Name(s) for empty iterables or false boolean flags. * For booleans, defaults to ``no-{name}`` (see :attr:`negative_bool`). * For iterables, defaults to ``empty-{name}`` (see :attr:`negative_iterable`). Set to an empty list or string to disable the creation of negative flags. Example usage: .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() @app.default def main(*, verbose: Annotated[bool, Parameter(negative="--quiet")] = False): print(f"{verbose=}") app() .. code-block:: console $ my-script --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────╮ │ --verbose --quiet [default: False] │ ╰────────────────────────────────────────────────────────────────╯ .. attribute:: negative_bool :type: Optional[str] :value: None Prefix for negative boolean flags. Defaults to ``"no-"``. .. attribute:: negative_iterable :type: Optional[str] :value: None Prefix for empty iterables (like lists and sets) flags. Defaults to ``"empty-"``. .. attribute:: allow_leading_hyphen :type: bool :value: False Allow parsing non-numeric values that begin with a hyphen ``-``. This is disabled (:obj:`False`) by default, allowing for more helpful error messages for unknown CLI options. .. attribute:: parse :type: Optional[bool] :value: True Attempt to use this parameter while parsing. Annotated parameter **must** be keyword-only. This is intended to be used with :ref:`meta apps ` for injecting values. .. attribute:: required :type: Optional[bool] :value: None Indicates that the parameter must be supplied. Defaults to inferring from the function signature; i.e. :obj:`False` if the parameter has a default, :obj:`True` otherwise. .. attribute:: show :type: Optional[bool] :value: None Show this parameter on the help screen. Defaults to :attr:`parse` value (default: :obj:`True`). .. attribute:: show_default :type: Optional[bool] :value: None If a variable has a default, display the default on the help page. Defaults to :obj:`None`, similar to :obj:`True`, but will not display the default if it's :obj:`None`. .. attribute:: show_choices :type: Optional[bool] :value: True If a variable has a set of choices, display the choices on the help page. .. attribute:: help :type: Optional[str] :value: None Help string to be displayed on the help page. If not specified, defaults to the docstring. .. attribute:: show_env_var :type: Optional[bool] :value: True If a variable has :attr:`env_var` set, display the variable name on the help page. .. attribute:: env_var :type: Union[None, str, Iterable[str]] :value: None Fallback to environment variable(s) if CLI value not provided. If multiple environment variables are given, the left-most environment variable **with a set value** will be used. If no environment variable is set, Cyclopts will fallback to the function-signature default. .. attribute:: env_var_split :type: Callable :value: cyclopts.env_var_split Function that splits up the read-in :attr:`~cyclopts.Parameter.env_var` value. The function must have signature: .. code-block:: python def env_var_split(type_: type, val: str) -> list[str]: ... where ``type_`` is the associated parameter type-hint, and ``val`` is the environment value. .. attribute:: name_transform :type: Optional[Callable[[str], str]] :value: None A function that converts python parameter names to their CLI command counterparts. The function must have signature: .. code-block:: python def name_transform(s: str) -> str: ... If :obj:`None` (default value), uses :func:`cyclopts.default_name_transform`. .. attribute:: accepts_keys :type: Optional[bool] :value: None If ``False``, treat the user-defined class annotation similar to a tuple. Individual class sub-parameters will not be addressable by CLI keywords. The class will consume enough tokens to populate all required positional parameters. Default behavior (``accepts_keys=True``): .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() class Image: def __init__(self, path, label): self.path = path self.label = label def __repr__(self): return f"Image(path={self.path!r}, label={self.label!r})" @app.default def main(image: Image): print(f"{image=}") app() .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ──────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────────────────╮ │ * IMAGE.PATH --image.path [required] │ │ * IMAGE.LABEL --image.label [required] │ ╰─────────────────────────────────────────────────────────────────────╯ $ my-program foo.jpg nature image=Image(path='foo.jpg', label='nature') $ my-program --image.path foo.jpg --image.label nature image=Image(path='foo.jpg', label='nature') Behavior when ``accepts_keys=False``: .. code-block:: python # Modify the default command function's signature. @app.default def main(image: Annotated[Image, Parameter(accepts_keys=False)]): print(f"{image=}") .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ──────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────────────────╮ │ * IMAGE --image [required] │ ╰─────────────────────────────────────────────────────────────────────╯ $ my-program foo.jpg nature image=Image(path='foo.jpg', label='nature') $ my-program --image foo.jpg nature image=Image(path='foo.jpg', label='nature') .. attribute:: consume_multiple :type: Optional[bool] :value: None When a parameter is **specified by keyword**, consume multiple elements worth of CLI tokens. Will consume tokens until the stream is exhausted, or an and :attr:`.allow_leading_hyphen` is False If ``False`` (default behavior), then only a single element worth of CLI tokens will be consumed. .. code-block:: python from cyclopts import App from pathlib import Path app = App() @app.default def rules(files: list[Path], ext: list[str] = []): pass app() .. code-block:: console $ cmd --ext .pdf --ext .html foo.md bar.md .. attribute:: json_dict :type: Optional[bool] :value: None Allow for the parsing of json-dict-strings as data. If :obj:`None` (default behavior), acts like :obj:`True`, **unless** the annotated type is union'd with :obj:`str`. When :obj:`True`, data will be parsed as json if the following conditions are met: 1. The parameter is specified as a keyword option; e.g. ``--movie``. 2. The referenced parameter is dataclass-like. 3. The first character of the token is a ``{``. .. attribute:: json_list :type: Optional[bool] :value: None Allow for the parsing of json-list-strings as data. If :obj:`None` (default behavior), acts like :obj:`True`, **unless** the annotated type has each element type :obj:`str`. When :obj:`True`, data will be parsed as json if the following conditions are met: 1. The referenced parameter is iterable (not including :obj:`str`). 2. The first character of the token is a ``[``. .. automethod:: combine .. automethod:: default .. autoclass:: cyclopts.Group :members: create_ordered A group of parameters and/or commands in a CLI application. .. attribute:: name :type: str :value: "" Group name used for the help-page and for group-referenced-by-string. This is a title, so the first character should be capitalized. If a name is not specified, it will not be shown on the help-page. .. attribute:: help :type: str :value: "" Additional documentation shown on the help-page. This will be displayed inside the group's panel, above the parameters/commands. .. attribute:: show :type: Optional[bool] :value: None Show this group on the help-page. Defaults to :obj:`None`, which will only show the group if a ``name`` is provided. .. attribute:: sort_key :type: Any :value: None Modifies group-panel display order on the help-page. 1. If :attr:`sort_key`, or any of it's contents, are ``Callable``, then invoke it ``sort_key(group)`` and apply the returned value to (2) if :obj:`None`, (3) otherwise. 2. For all groups with ``sort_key==None`` (default value), sort them alphabetically. These sorted groups will be displayed **after** ``sort_key != None`` list (see 3). 3. For all groups with ``sort_key!=None``, sort them by ``(sort_key, group.name)``. It is the user's responsibility that ``sort_key`` s are comparable. Example usage: .. code-block:: python from cyclopts import App, Group app = App() @app.command(group=Group("4", sort_key=5)) def cmd1(): pass @app.command(group=Group("3", sort_key=lambda x: 10)) def cmd2(): pass @app.command(group=Group("2", sort_key=lambda x: None)) def cmd3(): pass @app.command(group=Group("1")) def cmd4(): pass app() Resulting help-page: .. code-block:: text Usage: app COMMAND ╭─ 4 ────────────────────────────────────────────────────────────────╮ │ cmd1 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ 3 ────────────────────────────────────────────────────────────────╮ │ cmd2 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ 1 ────────────────────────────────────────────────────────────────╮ │ cmd4 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ 2 ────────────────────────────────────────────────────────────────╮ │ cmd3 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ .. attribute:: default_parameter :type: Optional[Parameter] :value: None Default :class:`Parameter` in the parameter-resolution-stack that goes between :attr:`.App.default_parameter` and the function signature's :obj:`Annotated` :class:`.Parameter`. The provided :class:`Parameter` is not allowed to have a :attr:`~Parameter.group` value. .. attribute:: validator :type: Optional[Callable] :value: None A function (or list of functions) that validates an :class:`.ArgumentCollection`. Example usage: .. code-block:: python def validator(argument_collection: ArgumentCollection): "Raise an exception if something is invalid." Validators are **not** invoked for command groups. .. autoclass:: cyclopts.Token .. attribute:: keyword :type: Optional[str] :value: None **Unadulterated** user-supplied keyword like ``--foo`` or ``--foo.bar.baz``; ``None`` when token was pared positionally. Could also be something like ``tool.project.foo`` if from non-cli sources. .. attribute:: value :type: str :value: "" The parsed token value (unadulterated). .. attribute:: source :type: str :value: "" Where the token came from; used for error message purposes. Cyclopts uses the string ``cli`` for cli-parsed tokens. .. attribute:: index :type: int :value: 0 The relative positional index in which the value was provided. .. attribute:: keys :type: tuple[str, ...] :value: () The additional parsed **python** variable keys from :attr:`keyword`. Only used for Arguments that take arbitrary keys. .. attribute:: implicit_value :type: Any :value: cyclopts.UNSET Final value that should be used instead of converting from :attr:`value`. Commonly used for boolean flags. Ignored if :obj:`~.UNSET`. .. autoclass:: cyclopts.field_info.FieldInfo .. autoclass:: cyclopts.Argument :members: .. autoclass:: cyclopts.ArgumentCollection :members: .. autoclass:: cyclopts.UNSET .. autofunction:: cyclopts.default_name_transform .. autofunction:: cyclopts.env_var_split .. autofunction:: cyclopts.edit .. autofunction:: cyclopts.run .. _API Validators: ---------- Validators ---------- Cyclopts has several builtin validators for common CLI inputs. .. autoclass:: cyclopts.validators.LimitedChoice :members: .. autoclass:: cyclopts.validators.MutuallyExclusive :members: .. autoclass:: cyclopts.validators.Number :members: .. autoclass:: cyclopts.validators.Path :members: .. _Annotated Types: ----- Types ----- Cyclopts has builtin pre-defined annotated-types for common conversion and validation configurations. All definitions in this section are simply predefined annotations for convenience: .. code-block:: python Annotated[..., Parameter(...)] Due to Cyclopts's advanced :class:`.Parameter` resolution engine, these annotations can themselves be annotated to further configure behavior. E.g: .. code-block:: Annotated[PositiveInt, Parameter(...)] .. _Annotated Path Types: ^^^^ Path ^^^^ :class:`~pathlib.Path` annotated types for checking existence, type, and performing path-resolution. All of these types will also work on sequence of paths (e.g. ``tuple[Path, Path]`` or ``list[Path]``). .. autodata:: cyclopts.types.ExistingPath .. autodata:: cyclopts.types.ResolvedPath .. autodata:: cyclopts.types.ResolvedExistingPath .. autodata:: cyclopts.types.Directory .. autodata:: cyclopts.types.ExistingDirectory .. autodata:: cyclopts.types.ResolvedDirectory .. autodata:: cyclopts.types.ResolvedExistingDirectory .. autodata:: cyclopts.types.File .. autodata:: cyclopts.types.ExistingFile .. autodata:: cyclopts.types.ResolvedFile .. autodata:: cyclopts.types.ResolvedExistingFile .. autodata:: cyclopts.types.BinPath .. autodata:: cyclopts.types.ExistingBinPath .. autodata:: cyclopts.types.CsvPath .. autodata:: cyclopts.types.ExistingCsvPath .. autodata:: cyclopts.types.TxtPath .. autodata:: cyclopts.types.ExistingTxtPath .. autodata:: cyclopts.types.ImagePath .. autodata:: cyclopts.types.ExistingImagePath .. autodata:: cyclopts.types.Mp4Path .. autodata:: cyclopts.types.ExistingMp4Path .. autodata:: cyclopts.types.JsonPath .. autodata:: cyclopts.types.ExistingJsonPath .. autodata:: cyclopts.types.TomlPath .. autodata:: cyclopts.types.ExistingTomlPath .. autodata:: cyclopts.types.YamlPath .. autodata:: cyclopts.types.ExistingYamlPath .. _Annotated Number Types: ^^^^^^ Number ^^^^^^ Annotated types for checking common int/float value constraints. All of these types will also work on sequence of numbers (e.g. ``tuple[int, int]`` or ``list[float]``). .. autodata:: cyclopts.types.PositiveFloat .. autodata:: cyclopts.types.NonNegativeFloat .. autodata:: cyclopts.types.NegativeFloat .. autodata:: cyclopts.types.NonPositiveFloat .. autodata:: cyclopts.types.PositiveInt .. autodata:: cyclopts.types.NonNegativeInt .. autodata:: cyclopts.types.NegativeInt .. autodata:: cyclopts.types.NonPositiveInt .. autodata:: cyclopts.types.UInt8 .. autodata:: cyclopts.types.Int8 .. autodata:: cyclopts.types.UInt16 .. autodata:: cyclopts.types.Int16 .. autodata:: cyclopts.types.UInt32 .. autodata:: cyclopts.types.Int32 ^^^^ Json ^^^^ Annotated types for parsing a json-string from the CLI. .. autodata:: cyclopts.types.Json .. _API Config: ------ Config ------ Cyclopts has builtin configuration classes to be used with :attr:`App.config ` for loading user-defined defaults in many common scenarios. All Cyclopts builtins index into the configuration file with the following rules: 1. Apply ``root_keys`` (if provided) to enter the project's configuration namespace. 2. Apply the command name(s) to enter the current command's configuration namespace. 3. Apply each key/value pair if CLI arguments have **not** been provided for that parameter. .. autoclass:: cyclopts.config.Toml Automatically read configuration from Toml file. .. attribute:: path :type: str | pathlib.Path Path to TOML configuration file. .. attribute:: root_keys :type: Iterable[str] :value: None The key or sequence of keys that lead to the root configuration structure for this app. For example, if referencing a ``pyproject.toml``, it is common to store all of your projects configuration under: .. code-block:: toml [tool.myproject] So, your Cyclopts :class:`~cyclopts.App` should be configured as: .. code-block:: python app = cyclopts.App(config=cyclopts.config.Toml("pyproject.toml", root_keys=("tool", "myproject"))) .. attribute:: must_exist :type: bool :value: False The configuration file MUST exist. Raises :class:`FileNotFoundError` if it does not exist. .. attribute:: search_parents :type: bool :value: False If ``path`` doesn't exist, iteratively search parenting directories for a same-named configuration file. Raises :class:`FileNotFoundError` if no configuration file is found. .. attribute:: allow_unknown :type: bool :value: False Allow for unknown keys. Otherwise, if an unknown key is provided, raises :class:`UnknownOptionError`. .. attribute:: use_commands_as_keys :type: bool :value: True Use the sequence of commands as keys into the configuration. For example, the following CLI invocation: .. code-block:: console $ python my-script.py my-command Would search into ``["my-command"]`` for values. .. autoclass:: cyclopts.config.Yaml Automatically read configuration from YAML file. .. attribute:: path :type: str | pathlib.Path Path to YAML configuration file. .. attribute:: root_keys :type: Iterable[str] :value: None The key or sequence of keys that lead to the root configuration structure for this app. For example, if referencing a common ``config.yaml`` that is shared with other applications, it is common to store your projects configuration under a key like ``myproject:``. Your Cyclopts :class:`~cyclopts.App` would be configured as: .. code-block:: python app = cyclopts.App(config=cyclopts.config.Yaml("config.yaml", root_keys="myproject")) .. attribute:: must_exist :type: bool :value: False The configuration file MUST exist. Raises :class:`FileNotFoundError` if it does not exist. .. attribute:: search_parents :type: bool :value: False If ``path`` doesn't exist, iteratively search parenting directories for a same-named configuration file. Raises :class:`FileNotFoundError` if no configuration file is found. .. attribute:: allow_unknown :type: bool :value: False Allow for unknown keys. Otherwise, if an unknown key is provided, raises :class:`UnknownOptionError`. .. attribute:: use_commands_as_keys :type: bool :value: True Use the sequence of commands as keys into the configuration. For example, the following CLI invocation: .. code-block:: console $ python my-script.py my-command Would search into ``["my-command"]`` for values. .. autoclass:: cyclopts.config.Json Automatically read configuration from Json file. .. attribute:: path :type: str | pathlib.Path Path to JSON configuration file. .. attribute:: root_keys :type: Iterable[str] :value: None The key or sequence of keys that lead to the root configuration structure for this app. For example, if referencing a common ``config.json`` that is shared with other applications, it is common to store your projects configuration under a key like ``"myproject":``. Your Cyclopts :class:`~cyclopts.App` would be configured as: .. code-block:: python app = cyclopts.App(config=cyclopts.config.Json("config.json", root_keys="myproject")) .. attribute:: must_exist :type: bool :value: False The configuration file MUST exist. Raises :class:`FileNotFoundError` if it does not exist. .. attribute:: search_parents :type: bool :value: False If ``path`` doesn't exist, iteratively search parenting directories for a same-named configuration file. Raises :class:`FileNotFoundError` if no configuration file is found. .. attribute:: allow_unknown :type: bool :value: False Allow for unknown keys. Otherwise, if an unknown key is provided, raises :class:`UnknownOptionError`. .. attribute:: use_commands_as_keys :type: bool :value: True Use the sequence of commands as keys into the configuration. For example, the following CLI invocation: .. code-block:: console $ python my-script.py my-command Would search into ``["my-command"]`` for values. .. autoclass:: cyclopts.config.Env Automatically derive environment variable names to read configurations from. For example, consider the following app: .. code-block:: python import cyclopts app = cyclopts.App(config=cyclopts.config.Env("MY_SCRIPT_")) @app.command def my_command(foo, bar): print(f"{foo=} {bar=}") app() If values for ``foo`` and ``bar`` are not supplied by the command line, the app will check the environment variables ``MY_SCRIPT_MY_COMMAND_FOO`` and ``MY_SCRIPT_MY_COMMAND_BAR``, respectively: .. code-block:: console $ python my_script.py my-command 1 2 foo=1 bar=2 $ export MY_SCRIPT_MY_COMMAND_FOO=100 $ python my_script.py my-command --bar=2 foo=100 bar=2 $ python my_script.py my-command 1 2 foo=1 bar=2 .. attribute:: prefix :type: str :value: "" String to prepend to all autogenerated environment variable names. Typically ends in ``_``, and is something like ``MY_APP_``. .. attribute:: command :type: bool :value: True If :obj:`True`, add the command's name (uppercase) after :attr:`prefix`. .. attribute:: split :type: Callable :value: cyclopts.env_var_split Function that splits up the read-in :attr:`~cyclopts.Parameter.env_var` value. ---------- Exceptions ---------- .. autoexception:: cyclopts.CycloptsError :show-inheritance: :members: .. autoexception:: cyclopts.ValidationError :show-inheritance: :members: .. autoexception:: cyclopts.UnknownOptionError :show-inheritance: :members: .. autoexception:: cyclopts.CoercionError :show-inheritance: :members: .. autoexception:: cyclopts.InvalidCommandError :show-inheritance: :members: .. autoexception:: cyclopts.UnusedCliTokensError :show-inheritance: :members: .. autoexception:: cyclopts.MissingArgumentError :show-inheritance: :members: .. autoexception:: cyclopts.RepeatArgumentError :show-inheritance: :members: .. autoexception:: cyclopts.MixedArgumentError :show-inheritance: :members: .. autoexception:: cyclopts.CommandCollisionError :show-inheritance: :members: .. autoexception:: cyclopts.EditorError :show-inheritance: :members: .. autoexception:: cyclopts.EditorNotFoundError :show-inheritance: :members: .. autoexception:: cyclopts.EditorDidNotSaveError :show-inheritance: :members: .. autoexception:: cyclopts.EditorDidNotChangeError :show-inheritance: :members: cyclopts-3.9.0/docs/source/app_calling.rst000066400000000000000000000074131475451620500206340ustar00rootroot00000000000000=========================== App Calling & Return Values =========================== In this section, we'll take a closer look at the :meth:`.App.__call__` method. ------------- Input Command ------------- Typically, a Cyclopts app looks something like: .. code-block:: python from cyclopts import App app = App() @app.command def foo(a: int, b: int, c: int): print(a + b + c) app() .. code-block:: console $ my-script 1 2 3 6 :meth:`.App.__call__` takes in an optional input that it parses into an action. If not specified, Cyclopts defaults to ``sys.argv[1:]``, i.e. the list of command line arguments. An explicit string or list of strings can instead be passed in. .. code-block:: python app("foo 1 2 3") # 6 app(["foo", "1", "2", "3"]) # 6 If a string is passed in, it will be internally converted into a list using `shlex.split `_. ------------ Return Value ------------ The ``app`` invocation returns the value of the called command. .. code-block:: python from cyclopts import App app = App() @app.command def foo(a: int, b: int, c: int): return a + b + c return_value = app("foo 1 2 3") print(f"The return value was: {return_value}.") # The return value was: 6. If you decide you want each command to return an exit code, you could invoke your app like: .. code-block:: python if __name__ == "__main__": sys.exit(app()) ------------------------------ Exception Handling and Exiting ------------------------------ For the most part, Cyclopts is **hands-off** when it comes to handling exceptions and exiting the application. However, by default, if there is a **Cyclopts runtime error**, like :exc:`.CoercionError` or a :exc:`.ValidationError`, then Cyclopts will perform a ``sys.exit(1)``. This is to avoid displaying the unformatted, uncaught exception to the CLI user. This can be disabled by specifying ``exit_on_error=False`` when calling the app. At the same time, you may want to set ``print_error=False`` to disable the printing of the formatted exception. .. code-block:: python app("this-is-not-a-registered-command") print("this will not be printed since cyclopts exited above.") # ╭─ Error ─────────────────────────────────────────────────────────────╮ # │ Unknown command "this-is-not-a-registered-command". │ # ╰─────────────────────────────────────────────────────────────────────╯ app("this-is-not-a-registered-command", exit_on_error=False, print_error=False) # Traceback (most recent call last): # File "/cyclopts/scratch.py", line 9, in # app("this-is-not-a-registered-command", exit_on_error=False, print_error=False) # File "/cyclopts/cyclopts/core.py", line 1102, in __call__ # command, bound, _ = self.parse_args( # File "/cyclopts/cyclopts/core.py", line 1037, in parse_args # command, bound, unused_tokens, ignored, argument_collection = self._parse_known_args( # File "/cyclopts/cyclopts/core.py", line 966, in _parse_known_args # raise InvalidCommandError(unused_tokens=unused_tokens) # cyclopts.exceptions.InvalidCommandError: Unknown command "this-is-not-a-registered-command". try: app("this-is-not-a-registered-command", exit_on_error=False, print_error=False) except CycloptsError: pass print("Execution continues since we caught the exception.") With ``exit_on_error=False``, the ``InvalidCommandError`` is raised the same as a normal python exception. cyclopts-3.9.0/docs/source/args_and_kwargs.rst000066400000000000000000000044551475451620500215220ustar00rootroot00000000000000.. _Args & Kwargs: ============= Args & Kwargs ============= In python, a function can consume a variable number of positional and keyword arguments: .. code-block:: python def foo(normal_required_variable, *args, **kwargs): pass There is **nothing special** about the names ``args`` and ``kwargs``; the functionality is derived from the leading ``*`` and ``**``. ``args`` and ``kwargs`` are the defacto standard names for these variables. In this document, we'll usually just refer to them as ``*args`` and ``**kwargs``. Cyclopts commands may consume a variable number of positional and keyword arguments. The priority ruleset is as follows: 1. ``--keyword`` CLI arguments first get matched to normal variable parameters. 2. Unmatched keywords get consumed by ``**kwargs``, if specified. 3. All remaining tokens get consumed by ``*args``, if specified. A prevalant use-case is in a typical :ref:`Meta App`. .. _Args & Kwargs - Args: -------------------------- Args (Variable Positional) -------------------------- A variable number of positional arguments consume all remaining positional arguments from the command-line. Individual elements are converted to the annotated type. .. code-block:: python from cyclopts import App app = App() @app.command def foo(name: str, *favorite_numbers: int): print(f"{name}'s favorite numbers are: {favorite_numbers}") app() .. code-block:: console $ my-script foo Brian Brian's favorite numbers are: () $ my-script foo Brian 777 Brian's favorite numbers are: (777,) $ my-script foo Brian 777 2 Brian's favorite numbers are: (777, 2) .. _Args & Kwargs - Kwargs: -------------------------- Kwargs (Variable Keywords) -------------------------- A variable number of keyword arguments consume all remaining CLI tokens starting with ``--``. Individual values are converted to the annotated type. .. code-block:: python from cyclopts import App app = App() @app.command def add(**country_to_capitols): for country, capitol in country_to_capitols.items(): print(f"Adding {country} with capitol {capitol}.") app() .. code-block:: console $ my-script add --united-states="Washington, D.C." --canada=Ottawa Adding united-states with capitol Washington, D.C.. Adding canada with capitol Ottawa. cyclopts-3.9.0/docs/source/autoregistry.rst000066400000000000000000000077301475451620500211260ustar00rootroot00000000000000============ AutoRegistry ============ AutoRegistry_ is a python library that automatically creates string-to-functionality mappings, making it trivial to instantiate classes or invoke functions from CLI parameters. Lets consider the following program that can download a file from either a GCP, AWS, or Azure bucket (without worrying about the implementation): .. code-block:: python import cyclopts from pathlib import Path from typing import Literal def _download_gcp(bucket: str, key: str, dst: Path): print("Downloading data from Google.") def _download_s3(bucket: str, key: str, dst: Path): print("Downloading data from Amazon.") def _download_azure(bucket: str, key: str, dst: Path): print("Downloading data from Azure.") _downloaders = { "gcp": _download_gcp, "s3": _download_s3, "azure": _download_azure, } app = cyclopts.App() @app.command def download(bucket: str, key: str, dst: Path, provider: Literal[tuple(_downloaders)] = "gcp"): downloader = _downloaders[provider] downloader(bucket, key, dst) app() .. code-block:: console $ my-script download --help ╭─ Parameters ────────────────────────────────────────────────────────────╮ │ * BUCKET,--bucket [required] │ │ * KEY,--key [required] │ │ * DST,--dst [required] │ │ PROVIDER,--provider [choices: gcp,s3,azure] [default: gcp] │ ╰─────────────────────────────────────────────────────────────────────────╯ $ my-script my-bucket my-key local.bin --provider=s3 Downloading data from Amazon. Not bad, but let's see how this would look with AutoRegistry. .. code-block:: python import cyclopts from autoregistry import Registry from pathlib import Path from typing import Literal _downloaders = Registry(prefix="_download_") @_downloaders def _download_gcp(bucket: str, key: str, dst: Path): print("Downloading data from Google.") @_downloaders def _download_s3(bucket: str, key: str, dst: Path): print("Downloading data from Amazon.") @_downloaders def _download_azure(bucket: str, key: str, dst: Path): print("Downloading data from Azure.") app = cyclopts.App() @app.command def download(bucket: str, key: str, dst: Path, provider: Literal[tuple(_downloaders)] = "gcp"): downloader = _downloaders[provider] downloader(bucket, key, dst) app() .. code-block:: console $ my-script download --help ╭─ Parameters ────────────────────────────────────────────────────────────╮ │ * BUCKET,--bucket [required] │ │ * KEY,--key [required] │ │ * DST,--dst [required] │ │ PROVIDER,--provider [choices: gcp,s3,azure] [default: gcp] │ ╰─────────────────────────────────────────────────────────────────────────╯ $ my-script my-bucket my-key local.bin --provider=s3 Downloading data from Amazon. Exactly the same functionality, but more terse and organized. With Autoregistry, the download providers are much more self-contained, do not require changes in other code locations, and reduce duplication. .. _AutoRegistry: https://github.com/BrianPugh/autoregistry cyclopts-3.9.0/docs/source/command_chaining.rst000066400000000000000000000025521475451620500216400ustar00rootroot00000000000000.. _Command Chaining: ================ Command Chaining ================ Cyclopts does not natively support command chaining. This is because Cyclopts opted for more flexible and robust CLI parsing, rather than a compromised, inconsistent parsing experience. With that said, Cyclopts gives you the tools to create your own command chaining experience. In this example, we will use a special delimiter token (e.g. ``"AND"``) to separate commands. .. code-block:: python import itertools from cyclopts import App, Parameter from typing import Annotated app = App() @app.command def foo(val: int): print(f"FOO {val=}") @app.command def bar(flag: bool): print(f"BAR {flag=}") @app.meta.default def main(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]): # tokens is ``["foo", "123", "AND", "foo", "456", "AND", "bar", "--flag"]`` delimiter = "AND" groups = [list(group) for key, group in itertools.groupby(tokens, lambda x: x == delimiter) if not key] or [[]] # groups is ``[['foo', '123'], ['foo', '456'], ['bar', '--flag']]`` for group in groups: # Execute each group app(group) if __name__ == "__main__": app.meta(["foo", "123", "AND", "foo", "456", "AND", "bar", "--flag"]) # FOO val=123 # FOO val=456 # BAR flag=True cyclopts-3.9.0/docs/source/commands.rst000066400000000000000000000144431475451620500201650ustar00rootroot00000000000000.. _Commands: ======== Commands ======== There are two different ways of registering functions: 1. :meth:`app.default ` - Registers an action for when no registered command is provided. This was previously demonstrated in :ref:`Getting Started`. A sub-app **cannot** be registered with :meth:`app.default `. If no ``default`` command is registered, Cyclopts will display the help-page. 2. :meth:`app.command ` - Registers a function or :class:`.App` as a command. This section will detail how to use the :meth:`@app.command ` decorator. --------------------- Registering a Command --------------------- The :meth:`@app.command ` decorator adds a **command** to a Cyclopts application. .. code-block:: python from cyclopts import App app = App() @app.command def fizz(n: int): print(f"FIZZ: {n}") @app.command def buzz(n: int): print(f"BUZZ: {n}") app() We can now control which command runs from the CLI: .. code-block:: console $ my-script fizz 3 FIZZ: 3 $ my-script buzz 4 BUZZ: 4 $ my-script fuzz ╭─ Error ────────────────────────────────────────────────────────────────────╮ │ Unknown command "fuzz". Did you mean "fizz"? │ ╰────────────────────────────────────────────────────────────────────────────╯ ------------------------ Registering a SubCommand ------------------------ The :meth:`app.command ` method can also register another Cyclopts :class:`.App` as a command. .. code-block:: python from cyclopts import App app = App() sub_app = App(name="foo") # "foo" would be a better variable name than "sub_app". # "sub_app" in this example emphasizes the name comes from name="foo". app.command(sub_app) # Registers sub_app to command "foo" # Or, as a one-liner: app.command(sub_app := App(name="foo")) @sub_app.command def bar(n: int): print(f"BAR: {n}") # Alternatively, access subapps from app like a dictionary. @app["foo"].command def baz(n: int): print(f"BAZ: {n}") app() .. code-block:: console $ my-script foo bar 3 BAR: 3 $ my-script foo baz 4 BAZ: 4 The subcommand may have their own registered ``default`` action. Cyclopts's command structure is fully recursive. .. _Command Changing Name: --------------------- Changing Command Name --------------------- By default, commands are registered to the python function's name with underscores replaced with hyphens. Any leading or trailing underscores will be stripped. For example, the function ``_foo_bar()`` will become the command ``foo-bar``. This renaming is done because CLI programs generally tend to use hyphens instead of underscores. The name transform can be configured by :attr:`App.name_transform `. For example, to make CLI command names be identical to their python function name counterparts, we can configure :class:`~cyclopts.App` as follows: .. code-block:: python from cyclopts import App app = App(name_transform=lambda s: s) @app.command def foo_bar(): # will now be "foo_bar" instead of "foo-bar" print("running function foo_bar") app() .. code-block:: console $ my-script foo_bar running function foo_bar Alternatively, the name can be **manually** changed in the :meth:`@app.command ` decorator. Manually set names are **not** subject to :attr:`App.name_transform `. .. code-block:: python from cyclopts import App app = App() @app.command(name="bar") def foo(): # function name will NOT be used. print("Hello World!") app() .. code-block:: console $ my-script bar Hello World! ----------- Adding Help ----------- There are a few ways to add a help string to a command: 1. If the function has a docstring, the **short description** will be used as the help string for the command. This is generally the preferred method of providing help strings. 2. If the registered command is a sub app, the sub app's :attr:`help ` field will be used. .. code-block:: python sub_app = App(name="foo", help="Help text for foo.") app.command(sub_app) 3. The :attr:`help ` field of :meth:`@app.command `. If provided, the docstring or subapp help field will **not** be used. .. code-block:: python from cyclopts import App app = App() @app.command def foo(): """Help string for foo.""" pass @app.command(help="Help string for bar.") def bar(): """This got overridden.""" app() .. code-block:: console $ my-script --help ╭─ Commands ────────────────────────────────────────────────────────────╮ │ bar Help string for bar. │ │ foo Help string for foo. │ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰───────────────────────────────────────────────────────────────────────╯ ----- Async ----- Cyclopts also works with **async** commands: .. code-block:: python import asyncio from cyclopts import App app = App() @app.command async def foo(): await asyncio.sleep(10) app() -------------------------- Decorated Function Details -------------------------- Cyclopts **does not modify the decorated function in any way**. The returned function is the **exact same function** being decorated and can be used exactly as if it were not decorated by Cyclopts. cyclopts-3.9.0/docs/source/conf.py000066400000000000000000000143651475451620500171340ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- import importlib import inspect import sys from datetime import date from pathlib import Path import git from sphinx.application import Sphinx from sphinx.ext.autodoc import Options from cyclopts import __version__ sys.path.insert(0, str(Path("../..").absolute())) git_repo = git.Repo(".", search_parent_directories=True) # type: ignore[reportPrivateImportUsage] git_commit = git_repo.head.commit # -- Project information ----------------------------------------------------- project = "cyclopts" copyright = f"{date.today().year}, Brian Pugh" author = "Brian Pugh" # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags release = __version__ # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "myst_parser", "sphinx_rtd_theme", "sphinx_rtd_dark_mode", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.linkcode", "sphinx_copybutton", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] smartquotes = False # user starts in light mode default_dark_mode = False # Myst myst_enable_extensions = [ "linkify", ] # Intersphinx intersphinx_mapping = { "pydantic": ("https://docs.pydantic.dev/latest/", None), "python": ("https://docs.python.org/3", None), "rich": ("https://rich.readthedocs.io/en/stable/", None), "typing_extensions": ("https://typing-extensions.readthedocs.io/en/latest/", None), "pytest": ("https://docs.pytest.org/en/latest", None), } # Napoleon settings napoleon_google_docstring = True napoleon_numpy_docstring = True napoleon_include_init_with_doc = False napoleon_include_private_with_doc = False napoleon_include_special_with_doc = True napoleon_use_admonition_for_examples = False napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_references = False napoleon_use_ivar = False napoleon_use_param = True napoleon_use_rtype = True napoleon_preprocess_types = False napoleon_type_aliases = None napoleon_attr_annotations = True # Autodoc autodoc_default_options = { "member-order": "bysource", "undoc-members": False, "exclude-members": "__weakref__", } autoclass_content = "both" # LinkCode code_url = f"https://github.com/BrianPugh/cyclopts/blob/{git_commit}" def linkcode_resolve(domain, info): """Link code to github. Modified from: https://github.com/python-websockets/websockets/blob/778a1ca6936ac67e7a3fe1bbe585db2eafeaa515/docs/conf.py#L100-L134 """ # Non-linkable objects from the starter kit in the tutorial. if domain == "js": return if domain != "py": raise ValueError("expected only Python objects") if not info.get("module"): # Documented via py:function:: return mod = importlib.import_module(info["module"]) if "." in info["fullname"]: objname, attrname = info["fullname"].split(".") obj = getattr(mod, objname) try: # object is a method of a class obj = getattr(obj, attrname) except AttributeError: # object is an attribute of a class return None else: obj = getattr(mod, info["fullname"]) try: file = inspect.getsourcefile(obj) lines = inspect.getsourcelines(obj) except TypeError: # e.g. object is a typing.Union return None if file is None: return None file = Path(file).resolve().relative_to(git_repo.working_dir) if file.parts[0] != "cyclopts": # e.g. object is a typing.NewType return None start, end = lines[1], lines[1] + len(lines[0]) - 1 return f"{code_url}/{file}#L{start}-L{end}" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] html_title = project html_logo = "../../assets/logo_512w.png" html_favicon = "../../assets/favicon-192.png" html_theme_options = { "logo_only": True, "version_selector": True, "prev_next_buttons_location": "bottom", "style_external_links": False, "vcs_pageview_mode": "", "style_nav_header_background": "#F7E5B9", # Toc options "collapse_navigation": True, "sticky_navigation": True, "navigation_depth": 4, "includehidden": True, "titles_only": False, } html_context = { # Github options "display_github": True, "github_user": "BrianPugh", "github_repo": "cyclopts", "github_version": "main", "conf_py_path": "/docs/source/", } html_css_files = [ "custom.css", ] # --- Other Custom Stuff def simplify_exception_signature( app: Sphinx, what: str, name: str, obj, options: Options, signature, return_annotation ): # Check if the object is an exception and modify the signature if what == "exception" and isinstance(obj, type) and issubclass(obj, BaseException): return ("", None) # Return an empty signature and no return annotation def remove_attrs_methods(app, what, name, obj, options, lines): lines[:] = [line for line in lines if not line.startswith("Method generated by attrs for")] def setup(app: Sphinx): app.connect("autodoc-process-signature", simplify_exception_signature) app.connect("autodoc-process-docstring", remove_attrs_methods) cyclopts-3.9.0/docs/source/config_file.rst000066400000000000000000000104231475451620500206220ustar00rootroot00000000000000.. _Config Files: ============ Config Files ============ For more complicated CLI applications, it is common to have an external user configuration file. For example, the popular python tools ``poetry``, ``ruff``, and ``pytest`` are all configurable from a ``pyproject.toml`` file. The :attr:`App.config ` attribute accepts a `callable `_ (or list of callables) that add (or remove) values to the parsed CLI tokens. The provided callable must have signature: .. code-block:: python def config(apps: List["App"], commands: Tuple[str, ...], arguments: ArgumentCollection): """Modifies the argument collection inplace with some injected values. Parameters ---------- apps: Tuple[App, ...] The application hierarchy that led to the current command function. The current command app is the last element of this tuple. commands: Tuple[str, ...] The CLI strings that led to the current command function. arguments: ArgumentCollection Complete ArgumentCollection for the app. Modify this collection inplace to influence values provided to the function. """ ... The provided ``config`` does not have to be a function; all the Cyclopts builtin configs are classes that implement the ``__call__`` method. The Cyclopts builtins offer good standard functionality for common configuration files like yaml or toml. ------------ TOML Example ------------ In this example, we create a small CLI tool that counts the number of times a given character occurs in a file. .. code-block:: python # character-counter.py import cyclopts from pathlib import Path app = cyclopts.App( name="character-counter", config=cyclopts.config.Toml( "pyproject.toml", # Name of the TOML File root_keys=["tool", "character-counter"], # The project's namespace in the TOML. # If "pyproject.toml" is not found in the current directory, # then iteratively search parenting directories until found. search_parents=True, ), ) @app.command def count(filename: Path, *, character="-"): print(filename.read_text().count(character)) app() Running this code without a ``pyproject.toml`` present: .. code-block:: console $ python character-counter.py count README.md 70 $ python character-counter.py count README.md --character=t 380 We can have the new default character be ``t`` by adding the following to ``pyproject.toml``: .. code-block:: toml [tool.character-counter.count] character = "t" Rerunning the app without a specified ``--character`` will result in using the toml-provided value: .. code-block:: console $ python character-counter.py count README.md 380 ---------------------------- Environment Variable Example ---------------------------- To automatically derive and read appropriate environment variables, use the :class:`cyclopts.config.Env` class. Continuing the above TOML example: .. code-block:: python # character-counter.py import cyclopts from pathlib import Path app = cyclopts.App( name="character-counter", config=cyclopts.config.Env( "CHAR_COUNTER_", # Every environment variable will begin with this. ), ) @app.command def count(filename: Path, *, character="-"): print(filename.read_text().count(character)) app() :class:`~cyclopts.config.Env` assembles the environment variable name by joining the following components (in-order): 1. The provided ``prefix``. In this case, it is ``"CHAR_COUNTER_"``. 2. The command and subcommand(s) that lead up to the function being executed. 3. The parameter's CLI name, with the leading ``--`` stripped, and hyphens ``-`` replaced with underscores ``_``. Running this code without a specified ``--character`` results in counting the default ``-`` character. .. code-block:: console $ python character-counter.py count README.md 70 By exporting a value to ``CHAR_COUNTER_COUNT_CHARACTER``, that value will now be used as the default: .. code-block:: console $ export CHAR_COUNTER_COUNT_CHARACTER=t $ python character-counter.py count README.md 380 $ python character-counter.py count README.md --character=q 3 cyclopts-3.9.0/docs/source/cookbook/000077500000000000000000000000001475451620500174325ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/cookbook/app_upgrade.rst000066400000000000000000000027631475451620500224630ustar00rootroot00000000000000=========== App Upgrade =========== It's best practice for users to install python-based CLIs via pipx_, where each application gets it's own python virtual environment. Whether done via ``pipx`` or standard ``pip``, updating your application can be done via the ``upgrade`` command. i.e.: .. code-block:: console $ pipx upgrade mypackage If you would like your CLI application to be able to upgrade itself, you can add the following command to your application: .. code-block:: python import mypackage import subprocess import sys from cyclopts import App app = App() @app.command def upgrade(): """Update mypackage to latest stable version.""" old_version = mypackage.__version__ subprocess.check_output([sys.executable, "-m", "pip", "install", "--upgrade", "pip"]) subprocess.check_output([sys.executable, "-m", "pip", "install", "--upgrade", "mypackage"]) res = subprocess.run([sys.executable, "-m", "mypackage", "--version"], stdout=subprocess.PIPE, check=True) new_version = res.stdout.decode().strip() if old_version == new_version: print(f"mypackage up-to-date (v{new_version}).") else: print(f"mypackage updated from v{old_version} to v{new_version}.") app() :obj:`sys.executable` points to the currently used python interpreter's path; if your package was installed via pipx_, then it points to the python interpreter in it's respective virtual environment. .. _pipx: https://github.com/pypa/pipx cyclopts-3.9.0/docs/source/cookbook/interactive_help.rst000066400000000000000000000104411475451620500235110ustar00rootroot00000000000000======================== Interactive Shell & Help ======================== Cyclopts has a builtin :meth:`interactive shell-like feature`: .. code-block:: python from cyclopts import App app = App() @app.command def foo(p1): """Foo Docstring. Parameters ---------- p1: str Foo's first parameter. """ print(f"foo {p1}") @app.command def bar(p1): """Bar Docstring. Parameters ---------- p1: str Bar's first parameter. """ print(f"bar {p1}") # A blocking call, launching an interactive shell. app.interactive_shell(prompt="cyclopts> ") To make the application still work as-expected from the CLI, it is more appropriate to set a command (or ``@app.default``) to launch the shell: .. code-block:: python @app.command def shell(): app.interactive_shell() if __name__ == "__main__": app() # Don't call ``app.interactive_shell()`` here. Special flags like ``--help`` and ``--version`` work in the shell, but could be a bit awkward for the root-help: .. code-block:: console $ python interactive-shell-demo.py Interactive shell. Press Ctrl-D to exit. cyclopts> --help Usage: interactive-shell-demo.py COMMAND ╭─ Parameters ──────────────────────────────────────────────────╮ │ --version Display application version. │ │ --help -h Display this message and exit. │ ╰───────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────╮ │ bar Bar Docstring. │ │ foo Foo Docstring. │ ╰───────────────────────────────────────────────────────────────╯ cyclopts> foo --help Usage: interactive-shell-demo.py foo [ARGS] [OPTIONS] Foo Docstring ╭─ Parameters ──────────────────────────────────────────────────╮ │ * P1,--p1 Foo's first parameter. [required] │ ╰───────────────────────────────────────────────────────────────╯ cyclopts> To resolve this, we can explicitly add a ``help`` command: .. code-block:: python @app.command def help(): """Display the help screen.""" app.help_print() .. code-block:: console $ python interactive-shell-demo.py Interactive shell. Press Ctrl-D to exit. cyclopts> help Usage: interactive-shell-demo.py COMMAND ╭─ Parameters ──────────────────────────────────────────────────╮ │ --version Display application version. │ │ --help -h Display this message and exit. │ ╰───────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────╮ │ bar Bar Docstring. │ │ foo Foo Docstring. │ │ help Display the help screen. │ ╰───────────────────────────────────────────────────────────────╯ cyclopts-3.9.0/docs/source/cookbook/rich_formatted_exceptions.rst000066400000000000000000000112701475451620500254200ustar00rootroot00000000000000========================= Rich Formatted Exceptions ========================= Tracebacks of uncaught exceptions provide valuable feedback for debugging. This guide demonstrates how to enhance your error messages using rich formatting. ------------------------- Standard Python Traceback ------------------------- Consider the following example: .. code-block:: python from cyclopts import App app = App() @app.default def main(name: str): print(name + 3) if __name__ == "__main__": app() Running this script will produce a standard Python traceback: .. code-block:: console $ python my-script.py foo Traceback (most recent call last): File "/cyclopts/my-script.py", line 12, in app() File "/cyclopts/cyclopts/core.py", line 903, in __call__ return command(*bound.args, **bound.kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/cyclopts/my-script.py", line 8, in main print(name + 3) ~~~~~^~~ TypeError: can only concatenate str (not "int") to str ------------------------ Rich Formatted Traceback ------------------------ To create a more visually appealing and informative traceback, you can use the `Rich library's traceback handler`_. Here's how to modify your script: .. code-block:: python from cyclopts import App from rich.console import Console console = Console() app = App(console=console) # Use same Console object for Cyclopts operations. @app.default def main(name: str): print(name + 3) if __name__ == "__main__": try: app() except Exception: console.print_exception() Now, running the updated script will display a rich-formatted traceback: .. code-block:: console $ python my-script.py foo ╭──────────────── Traceback (most recent call last) ─────────────────╮ │ /cyclopts/my-script.py:16 in │ │ │ │ 13 │ │ 14 if __name__ == "__main__": │ │ 15 │ try: │ │ ❱ 16 │ │ app() │ │ 17 │ except Exception: │ │ 18 │ │ console.print_exception(width=70) │ │ 19 │ │ │ │ /cyclopts/cyclopts/core.py:903 in __call__ │ │ │ │ 900 │ │ │ │ │ │ 901 │ │ │ │ return asyncio.run(command(*bound.args, **b │ │ 902 │ │ │ else: │ │ ❱ 903 │ │ │ │ return command(*bound.args, **bound.kwargs) │ │ 904 │ │ except Exception as e: │ │ 905 │ │ │ try: │ │ 906 │ │ │ │ from pydantic import ValidationError as Pyd │ │ │ │ /cyclopts/my-script.py:11 in main │ │ │ │ 8 │ │ 9 @app.default │ │ 10 def main(name: str): │ │ ❱ 11 │ print(name + 3) │ │ 12 │ │ 13 │ │ 14 if __name__ == "__main__": │ ╰────────────────────────────────────────────────────────────────────╯ This rich-formatted traceback provides a more readable and visually appealing representation of the error, but may make copy/pasting for sharing a bit more cumbersome. .. _Rich library's traceback handler: https://rich.readthedocs.io/en/stable/traceback.html#printing-tracebacks cyclopts-3.9.0/docs/source/cookbook/sharing_parameters.rst000066400000000000000000000127021475451620500240440ustar00rootroot00000000000000================== Sharing Parameters ================== Many subcommands within a CLI may take the same parameters. For example, all commands for a CLI that deals with a remote server might need a ``url`` and ``port`` number. Furthermore, there might be common setup required, such as connecting to the remote server. If you are familiar with `Click`_, this would be accomplished with `contexts `_. In Cyclopts, there are 2 ways to accomplish this: 1. With a :ref:`meta app `. While powerful, it's admittantly a bit heavy-handed and clunky. 2. Via a common dataclass that is passed to each command. While less powerful than using a meta-app, it still accomplishes many of the same goals with simpler, terser code. In this section, we'll be investigating option (2) by constructing an example application that has 2 commands: 1. ``create`` - Connect to a server and send a POST command to it. 2. ``info`` - Connect to a server and GET information about a user. .. code-block:: python # demo.py from cyclopts import App, Parameter from cyclopts.types import UInt16 from dataclasses import dataclass from functools import cached_property from httpx import Client @Parameter(name="*") # Flatten the namespace; i.e. option will be "--url" instead of "--common.url" @dataclass class Common: url: str = "http://cyclopts.readthedocs.io" "URL of remote server." port: UInt16 = 8080 # an "int" that is limited to range [0, 65535] "Port of remote server." verbose: bool = False "Increased printing verbosity." def __post_init__(self): # dataclasses call this method after calling the auto-generated __init__. if self.verbose: print(f"Server: {self.base_url}") @property def base_url(self) -> str: return f"{self.url}:{self.port}" @cached_property def client(self) -> Client: return Client(base_url=self.base_url) app = App() @app.command def create(name: str, age: int, *, common: Common | None = None): """Create a user on remote server. Parameters ---------- name: str Name of the user to create. age: int Age of the user in years. """ if common is None: common = Common() json = {"name": name, "age": age} if common.verbose: print(f"Creating user: {json}") common.client.post("/users", json=json) # TODO: in a real application, we should error-check the response here. @app.command def info(name: str, *, common: Common | None = None): """List a user on remote server. Parameters ---------- name: str Name of the user to get info about. """ if common is None: common = Common() response = common.client.get("/users", params={"name": name}) user = response.json() print(f"User: {user}") if __name__ == "__main__": app() From the root help-page, we can see our two commands: .. code-block:: console $ python demo.py --help Usage: demo.py COMMAND ╭─ Commands ─────────────────────────────────────────────────────────────────╮ │ create Create a user on remote server. │ │ info List a user on remote server. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────────────╯ From the ``create`` help-page, we can see all of our parameters: .. code-block:: console $ python demo.py create --help Usage: demo.py create [ARGS] [OPTIONS] Create a user on remote server. ╭─ Parameters ───────────────────────────────────────────────────────────────╮ │ * NAME --name Name of the user to create. [required] │ │ * AGE --age Age of the user in years. [required] │ │ --url URL of remote server. [default: │ │ http://cyclopts.readthedocs.io] │ │ --port Port of remote server. [default: 8080] │ │ --verbose --no-verbose Increased printing verbosity. [default: False] │ ╰────────────────────────────────────────────────────────────────────────────╯ Some example command-line invocations: .. code-block:: console $ python demo.py create Alice 42 # No response from the CLI. $ python demo.py create Alice 42 --verbose Creating user: {'name': 'Alice', 'age': 42} By organizing the code this way, we can centralize shared parameters and logic between many commands. .. _Click: https://click.palletsprojects.com cyclopts-3.9.0/docs/source/cookbook/unit_testing.rst000066400000000000000000000330411475451620500227010ustar00rootroot00000000000000============ Unit Testing ============ It is important to have unit-tests to verify that your CLI is behaving correctly. For unit-testing, we will be using the defacto-standard python unit-testing library, pytest_. This section demonstrates some common scenarios you may encounter when unit-testing your CLI app. Lets make a small application that checks PyPI_ if a library name is available: .. code-block:: python # pypi_checker.py import sys import urllib.error import urllib.request import cyclopts def _check_pypi_name_available(name): try: urllib.request.urlopen(f"https://pypi.org/pypi/{name}/json") except urllib.error.HTTPError as e: if e.code == 404: return True # Package does not exist (name is available) return False # Package exists (name is not available) app = cyclopts.App( config=[ cyclopts.config.Env("PYPI_CHECKER_"), cyclopts.config.Json("config.json"), ], ) @app.default def pypi_checker(name: str, *, silent: bool = False): """Check if a package name is available on PyPI. Exit code 0 on success; non-zero otherwise. Parameters ---------- name: str Name of the package to check. silent: bool Do not print anything to stdout. """ is_available = _check_pypi_name_available(name) if not silent: if is_available: print(f"{name} is available.") else: print(f"{name} is not available.") sys.exit(not is_available) if __name__ == "__main__": app() Running the app from the console: .. code-block:: console $ python pypi_checker.py --help Usage: pypi_checker COMMAND [ARGS] [OPTIONS] Check if a package name is available on PyPI. Exit code 0 on success; non-zero otherwise. ╭─ Commands ────────────────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ──────────────────────────────────────────────────────────────────╮ │ * NAME --name Name of the package to check. [required] │ │ --silent --no-silent Do not print anything to stdout. [default: False] │ ╰───────────────────────────────────────────────────────────────────────────────╯ $ python pypi_checker.py cyclopts cyclopts is not available. $ python pypi_checker.py cyclopts --silent $ echo $? # Check the exit code of the previous command. 1 $ python pypi_checker.py the-next-big-project the-next-big-project is available. $ echo $? # Check the exit code of the previous command. 0 We will slowly introduce unit-testing concepts and build up a fairly comprehensive set of unit-tests for this application. ------- Mocking ------- First off, it's good code-hygiene to separate "business logic" from "user interface." In this example, that means putting all the actual logic of determining whether or not a package name is available into the ``_check_pypi_name_available`` function, and putting all of the CLI logic (like printing to ``stdout`` and exit-codes) in the Cyclopts-decorated function ``pypi_checker``. This makes it easier to unit-test the app because it allows us to `mock `_ out portions of our app, allowing us to isolate our CLI unit-tests to just the CLI components. We can use `pytest-mock`_ to simplify mocking ``_check_pypi_name_available``. Let's define a `fixture`_ that declares this mock. .. code-block:: python # test.py import pytest from pypi_checker import app @pytest.fixture def mock_check_pypi_name_available(mocker): return mocker.patch("pypi_checker._check_pypi_name_available") Unit tests that use this fixture can define it's return value, as well as check the arguments it was called with. This will be demonstrated in the next section. ---------- Exit Codes ---------- Our app directly calls :func:`sys.exit`. Internal to python, this causes the :exc:`SystemExit` exception to be raised. We can catch this with the :func:`pytest.raises` context manager, and check the resulting error-code. .. code-block:: python def test_unavailable_name(mock_check_pypi_name_available): mock_check_pypi_name_available.return_value = False with pytest.raises(SystemExit) as e: app("foo") # Invoke our app, passing in package-name "foo" mock_check_pypi_name_available.assert_called_once_with("foo") # assert that our mock was called. assert e.value.code != 0 # assert the exit code is non-zero (i.e. not successful) We can then run pytest on this file: .. code-block:: console $ pytest test.py ============================== test session starts ============================== platform darwin -- Python 3.13.0, pytest-8.3.4, pluggy-1.5.0 rootdir: /cyclopts-demo configfile: pyproject.toml plugins: cov-6.0.0, anyio-4.8.0, mock-3.14.0 collected 1 item test.py . [100%] =============================== 1 passed in 0.05s =============================== .. note:: Alternatively, we could have avoided using :func:`sys.exit` within our commands, and have our commands instead return an integer error-code. .. code-block:: python # pypi_checker.py @app.default def pypi_checker(name: str, *, silent: bool = False): ... return not is_available if __name__ == "__main__": sys.exit(app()) With this setup, our unit-test would just have to check: .. code-block:: python # test.py assert app("foo") != 0 --------------- Checking stdout --------------- We also want to make sure that our message is displayed to the user. The built-in `capsys`_ fixture gives us access to our application's ``stdout``. We can use this to confirm our app prints the correct statement. .. code-block:: python # test.py - continued from "Mocking" def test_unavailable_name(capsys, mock_check_pypi_name_available): mock_check_pypi_name_available.return_value = False with pytest.raises(SystemExit) as e: app("foo") # Invoke our app, passing in package-name "foo" mock_check_pypi_name_available.assert_called_once_with("foo") # assert that our mock was called. assert e.value.code != 0 # assert the exit code is non-zero (i.e. not successful) assert capsys.readouterr().out == "foo is not available.\n" --------------------- Environment Variables --------------------- Because we configured our :class:`.App` with :class:`cyclopts.config.Env`, we can pass arguments into our application via environment variables. The `pytest monkeypatch fixture`_ allows us to modify environment variables within the context of a unit-test. In this test, we only want to test if our environment variable is being passed in correctly. We will use :meth:`.App.parse_args`, which performs all the parsing, but doesn't actually invoke the command. .. code-block:: python # test.py def test_name_env_var(monkeypatch): from pypi_checker import pypi_checker monkeypatch.setenv("PYPI_CHECKER_NAME", "foo") command, bound, _ = app.parse_args([]) # An empty list - no CLI arguments passed in. assert command == pypi_checker assert bound.arguments['name'] == "foo" .. warning:: A common mistake is accidentally calling ``app()`` or ``app.parse_args()`` with the **intent of providing no arguments**. Calling these methods with no arguments will read from :obj:`sys.argv`, the same as in a typical application. This is rarely the intention in a unit-test, and Cyclopts **will produce a warning.** For example, this code in a unit test: .. code-block:: python app() # Wrong: will produce a warning Will generate this warning: .. code-block:: text =============================== warnings summary ================================ test.py::test_no_args /my_project/test.py:64: UserWarning: Cyclopts application invoked without tokens under unit-test framework "pytest". Did you mean "app([])"? app() The proper way to specify no CLI arguments is to provide an empty string or list: .. code-block:: python app([]) ----------- File Config ----------- To explicitly test that configurations from the :ref:`Cyclopts configuration system ` are loading properly, we can create a configuration file in a temporary directory and change our current-working-directory (cwd) to that temporary directory. The pytest built-in ``tmp_path`` fixture gives us a temporary directory, and the ``monkeypatch`` fixture allows us to change the cwd. We have to change the cwd because typically configuration files are discovered relative to the directory where the CLI was invoked. If your CLI searches other locations (such as the home directory), you will need to modify this example appropriately. .. code-block:: python # test.py import json @pytest.fixture(autouse=True) def chdir_to_tmp_path(tmp_path, monkeypatch): "Automatically change current directory to tmp_path" monkeypatch.chdir(tmp_path) @pytest.fixture def config_path(tmp_path): "Path to JSON configuration file in tmp_path" return tmp_path / "config.json" # same name that was provided to cyclopts.config.Json def test_config(config_path): with config_path.open("w") as f: json.dump({"name": "bar"}, f) command, bound, _ = app.parse_args([]) # An empty list - no CLI arguments passed in. assert command == pypi_checker assert bound.arguments['name'] == "foo" --------- Help Page --------- Cyclopts uses Rich_ to pretty-print messages to the console. Rich interprets the console environment, and can change how it displays text depending on the terminal's capabilities. For unit testing, we will explicitly set a lot of these parameters in a pytest fixture to make it easier to compare against known good values: .. code-block:: python @pytest.fixture def console(): from rich.console import Console return Console(width=70, force_terminal=True, highlight=False, color_system=None, legacy_windows=False) Since the help-page is just printed to ``stdout``, we will be using the `capsys`_ fixture again. .. code-block:: python from textwrap import dedent def test_help_page(capsys, console): app("--help", console=console) actual = capsys.readouterr().out assert actual == dedent( """\ Usage: pypi-checker COMMAND [ARGS] [OPTIONS] Check if a package name is available on PyPI. Exit code 0 on success; non-zero otherwise. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * NAME --name Name of the package to check. [required] │ │ --silent --no-silent Do not print anything to stdout. │ │ [default: False] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) The :func:`textwrap.dedent` function allows us to have our expected-help-string nicely indented within our code. Alternatively, we could have used the :meth:`rich.console.Console.capture` context manager to directly capture the :class:`rich.console.Console` output. .. note:: Unit-testing the help-page is probably overkill for most projects (and may get in the way more often than it helps!). .. _PyPI: https://pypi.org .. _pytest: https://docs.pytest.org/en/stable/ .. _pytest-mock: https://pytest-mock.readthedocs.io/en/latest/ .. _fixture: https://docs.pytest.org/en/stable/explanation/fixtures.html .. _capsys: https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html#accessing-captured-output-from-a-test-function .. _pytest monkeypatch fixture: https://docs.pytest.org/en/stable/how-to/monkeypatch.html .. _Rich: https://rich.readthedocs.io/en/stable/ cyclopts-3.9.0/docs/source/default_parameter.rst000066400000000000000000000054271475451620500220520ustar00rootroot00000000000000.. _Default Parameter: ================= Default Parameter ================= The default values of :class:`Parameter` for an app can be configured via :attr:`.App.default_parameter`. For example, to disable the :attr:`~.Parameter.negative` flag feature across your entire app: .. code-block:: python from cyclopts import App, Parameter app = App(default_parameter=Parameter(negative=())) @app.command def foo(*, flag: bool): pass app() Consequently, ``--no-flag`` is no longer an allowed flag: .. code-block:: $ my-script foo --help Usage: my-script foo [ARGS] [OPTIONS] ╭─ Parameters ──────────────────────────────────────────────────╮ │ * --flag [required] │ ╰───────────────────────────────────────────────────────────────╯ Explicitly annotating the parameter with :attr:`~.Parameter.negative` overrides this configuration and works as expected: .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App(default_parameter=Parameter(negative=())) @app.command def foo(*, flag: Annotated[bool, Parameter(negative="--anti-flag")]): pass app() .. code-block:: console $ my-script foo --help Usage: my-script foo [ARGS] [OPTIONS] ╭─ Parameters ──────────────────────────────────────────────────╮ │ * --flag --anti-flag [required] │ ╰───────────────────────────────────────────────────────────────╯ .. _Parameter Resolution Order: ---------------- Resolution Order ---------------- When resolving what the :class:`.Parameter` values for an individual function parameter should be, explicitly set attributes of higher priority :class:`.Parameter` s override lower priority :class:`.Parameter` s. The resolution order is as follows: 1. **Highest Priority:** Parameter-annotated command function signature ``Annotated[..., Parameter()]``. 2. :attr:`.Group.default_parameter` that the **parameter** belongs to. 3. :attr:`.App.default_parameter` of the **app** that registered the command. 4. :attr:`.Group.default_parameter` of the **app** that the function belongs to. 5. **Lowest Priority:** (2-4) recursively of the parenting app call-chain. Any of Parameter's fields can be set to `None` to revert back to the true-original Cyclopts default. cyclopts-3.9.0/docs/source/getting_started.rst000066400000000000000000000215171475451620500215530ustar00rootroot00000000000000.. _Getting Started: =============== Getting Started =============== Cyclopts relies heavily on function parameter type hints. If you are new to type hints or need a refresher, `checkout the mypy cheatsheet`_. ---------------------------- A Basic Cyclopts Application ---------------------------- The most basic Cyclopts application is as follows: .. code-block:: python from cyclopts import App app = App() @app.default def main(): print("Hello World!") if __name__ == "__main__": app() Save this as ``main.py`` and execute it to see: .. code-block:: console $ python main.py Hello World! The :class:`.App` class offers various configuration options that we'll explore in more detail later. The ``app`` object has a decorator method, :meth:`default `, which registers a function as the **default action**. In this example, the ``main`` function is our default action, and is executed when no CLI command is provided. ------------------ Function Arguments ------------------ Let's add some arguments to make this program a little more interesting. .. code-block:: python from cyclopts import App app = App() @app.default def main(name): print(f"Hello {name}!") if __name__ == "__main__": app() Executing the script with the argument ``Alice`` produces the following: .. code-block:: console $ python main.py Alice Hello Alice! Code explanation: 1. The function ``main()`` was registered to ``app`` as the **default** action. 2. Calling ``app()`` at the bottom triggers the app to begin parsing CLI inputs. 3. Cyclopts identifies ``"Alice"`` as a positional argument and matches it to the parameter ``name``. In the absence of an explicit type hint, Cyclopts defaults to parsing the value as a ``str``. .. note:: Without a type annotation, Cyclopts will actually first attempt to use the **type** of the parameter's **default value**. If the parameter doesn't have a default value, it will then fallback to ``str``. See :ref:`Coercion Rules`. 4. Cyclopts calls the registered **default** function ``main("Alice")``, and the greeting is printed. ------------------ Multiple Arguments ------------------ Extending the example, lets add more arguments and type hints: .. code-block:: python from cyclopts import App app = App() @app.default def main(name: str, count: int, formal: bool = False): for _ in range(count): if formal: print(f"Hello {name}!") else: print(f"Hey {name}!") if __name__ == "__main__": app() .. code-block:: console $ python main.py Alice 3 Hey Alice! Hey Alice! Hey Alice! $ python main.py Alice 3 --formal Hello Alice! Hello Alice! Hello Alice! The command line input ``"3"`` is converted to an integer because the parameter ``count`` has the type hint :obj:`int`. Boolean ( here, ``--formal``) parameters are interpreted as flags. Cyclopts natively handles all python builtin types (:ref:`and more! `). Cyclopts adheres to Python's argument binding rules, allowing for both positional and keyword arguments. All of the following CLI invocations are equivalent: .. code-block:: console $ python main.py Alice 3 # Supplying arguments positionally. $ python main.py --name Alice --count 3 # Supplying arguments via keywords. $ python main.py --name=Alice --count=3 # Using = for matching keywords to values is allowed. $ python main.py --count 3 --name=Alice # Keyword order does not matter. $ python main.py Alice --count 3 # positional followed by keyword $ python main.py --count 3 Alice # Keywords can come before positional if the keyword is later in the function signature. Like calling functions in python, positional arguments cannot be specified after a **prior** argument in the function signature was specified via keyword. For example, you cannot supply the count value ``"3"`` positionally while the value for ``name`` is specified via keyword: .. code-block:: bash # The following are NOT allowed. $ python main.py --name=Alice 3 # invalid python: main(name="Alice", 3) $ python main.py 3 --name=Alice # invalid python: main(3, name="Alice") ------------------ Adding a Help Page ------------------ All CLI apps need to have a help page explaining how to use the application. By default, Cyclopts adds the ``--help`` (and the shortform ``-h``) commands to your CLI. We can add application-level help documentation when creating our ``app``: .. code-block:: python from cyclopts import App app = App(help="Help string for this demo application.") @app.default def main(name: str, count: int): for _ in range(count): print(f"Hello {name}!") if __name__ == "__main__": app() .. code-block:: console $ python main.py --help Usage: main COMMAND [ARGS] [OPTIONS] Help string for this demo application. ╭─ Commands ──────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────────────────╮ │ * NAME --name [required] │ │ * COUNT --count [required] │ ╰─────────────────────────────────────────────────────────────────────╯ .. note:: Help flags can be changed with :attr:`~cyclopts.App.help_flags`. Let's add some help documentation for our parameters. Cyclopts uses the function's docstring and can interpret ReST, Google, Numpydoc-style and Epydoc docstrings (shoutout to `docstring_parser `_). .. code-block:: python from cyclopts import App app = App() @app.default def main(name: str, count: int): """Help string for this demo application. Parameters ---------- name: str Name of the user to be greeted. count: int Number of times to greet. """ for _ in range(count): print(f"Hello {name}!") if __name__ == "__main__": app() .. code-block:: console $ python main.py --help Usage: main COMMAND [ARGS] [OPTIONS] Help string for this demo application. ╭─ Commands ──────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────────────────╮ │ * NAME --name Name of the user to be greeted. [required] │ │ * COUNT --count Number of times to greet. [required] │ ╰─────────────────────────────────────────────────────────────────────╯ .. note:: If :attr:`.App.help` is not explicitly set, Cyclopts will fallback to the first line (short description) of the registered ``@app.default`` function's docstring. --- Run --- An alternative, terser API is available for simple applications with a single command. The :func:`.run` function takes in a single callable (usually a function) and runs it as a Cyclopts application. .. code-block:: python import cyclopts def main(name: str, count: int): for _ in range(count): print(f"Hello {name}!") if __name__ == "__main__": cyclopts.run(main) The :func:`.run` function is intentionally simple. If greater control is required, then use the conventional :class:`.App` interface. .. _checkout the mypy cheatsheet: https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html cyclopts-3.9.0/docs/source/group_validators.rst000066400000000000000000000063531475451620500217510ustar00rootroot00000000000000.. _Group Validators: ================ Group Validators ================ Group validators operate on a set of parameters, :ref:`ensuring that their values are mutually compatible `. Cyclopts has some builtin common group validators in the :ref:`cyclopts.validators ` module. .. _Group Validators - LimitedChoice: ------------- LimitedChoice ------------- Limits the number of specified arguments within the group. Most commonly used for mutually-exclusive arguments (default behavior). .. code-block:: python from cyclopts import App, Group, Parameter, validators from typing import Annotated app = App() vehicle = Group( "Vehicle (choose one)", default_parameter=Parameter(negative=""), # Disable "--no-" flags validator=validators.LimitedChoice(), # Mutually Exclusive Options ) @app.default def main( *, car: Annotated[bool, Parameter(group=vehicle)] = False, truck: Annotated[bool, Parameter(group=vehicle)] = False, ): if car: print("I'm driving a car.") if truck: print("I'm driving a truck.") app() .. code-block:: console $ python drive.py --help Usage: main COMMAND [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Vehicle (choose one) ─────────────────────────────────────────────╮ │ --car [default: False] │ │ --truck [default: False] │ ╰────────────────────────────────────────────────────────────────────╯ $ python drive.py --car I'm driving a car. $ python drive.py --car --truck ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid values for group "Vehicle (choose one)". Mutually │ │ exclusive arguments: {--car, --truck} │ ╰────────────────────────────────────────────────────────────────────╯ See the :class:`.LimitedChoice` docs for more info. ----------------- MutuallyExclusive ----------------- Alias for :class:`.LimitedChoice` with default arguments. Exists primarily because the usage/implication will be more directly obvious and searchable to developers than :class:`.LimitedChoice`. cyclopts-3.9.0/docs/source/groups.rst000066400000000000000000000360531475451620500177040ustar00rootroot00000000000000.. _Groups: ====== Groups ====== Groups offer a way of organizing parameters and commands on the help-page; for example: .. code-block:: console Usage: my-script.py create [OPTIONS] ╭─ Vehicle (choose one) ───────────────────────────────────────────────────────╮ │ --car [default: False] │ │ --truck [default: False] │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Engine ─────────────────────────────────────────────────────────────────────╮ │ --hp [default: 200] │ │ --cylinders [default: 6] │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Wheels ─────────────────────────────────────────────────────────────────────╮ │ --wheel-diameter [default: 18] │ │ --rims,--no-rims [default: False] │ ╰──────────────────────────────────────────────────────────────────────────────╯ They also provide an additional abstraction layer that :ref:`validators ` can operate on. Groups can be created in two ways: 1. Explicitly creating a :class:`.Group` object. 2. Implicitly with a **string**. This will implicitly create a group, ``Group(my_str_group_name)``, if it doesn't exist. If there exists a :class:`.Group` object with the same name within the command/parameter context, it will join that group. .. warning:: While convenient and terse, mistyping a group name as a string will unintentionally create a new group! Every command and parameter belongs to at least one group. Group(s) can be provided to the ``group`` keyword argument of :meth:`app.command ` and :class:`.Parameter`. Like :class:`.Parameter`, the :class:`.Group` class itself only marks objects with metadata; the group does **not** contain direct references to it's members. This means that groups can be re-used across commands. -------------- Command Groups -------------- An example of using groups to organize commands: .. code-block:: python from cyclopts import App app = App() # Change the group of "--help" and "--version" to the implicitly created "Admin" group. app["--help"].group = "Admin" app["--version"].group = "Admin" @app.command(group="Admin") def info(): """Print debugging system information.""" print("Displaying system info.") @app.command def download(path, url): """Download a file.""" print(f"Downloading {url} to {path}.") @app.command def upload(path, url): """Upload a file.""" print(f"Downloading {url} to {path}.") app() .. code-block:: console $ python my-script.py --help Usage: my-script.py COMMAND ╭─ Admin ──────────────────────────────────────────────────────────────────────╮ │ info Print debugging system information. │ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ───────────────────────────────────────────────────────────────────╮ │ download Download a file. │ │ upload Upload a file. │ ╰──────────────────────────────────────────────────────────────────────────────╯ The default group is defined by the registering app's :attr:`.App.group_commands`, which defaults to a group named ``"Commands"``. .. _Parameter Groups: ---------------- Parameter Groups ---------------- Like commands above, parameter groups allow us to organize parameters on the help page. They also allow us to add additional inter-parameter validators (e.g. mutually-exclusive parameters). An example of using groups with parameters: .. code-block:: python from cyclopts import App, Group, Parameter, validators from typing import Annotated app = App() vehicle_type_group = Group( "Vehicle (choose one)", default_parameter=Parameter(negative=""), # Disable "--no-" flags validator=validators.MutuallyExclusive(), # Only one option is allowed to be selected. ) @app.command def create( *, # force all subsequent variables to be keyword-only # Using an explicitly created group object. car: Annotated[bool, Parameter(group=vehicle_type_group)] = False, truck: Annotated[bool, Parameter(group=vehicle_type_group)] = False, # Implicitly creating an "Engine" group. hp: Annotated[float, Parameter(group="Engine")] = 200, cylinders: Annotated[int, Parameter(group="Engine")] = 6, # You can explicitly create groups in-line. wheel_diameter: Annotated[float, Parameter(group=Group("Wheels"))] = 18, # Groups within the function signature can always be referenced with a string. rims: Annotated[bool, Parameter(group="Wheels")] = False, ): pass app() .. code-block:: console $ python my-script.py create --help Usage: my-script.py create [OPTIONS] ╭─ Engine ──────────────────────────────────────────────────────╮ │ --hp [default: 200] │ │ --cylinders [default: 6] │ ╰───────────────────────────────────────────────────────────────╯ ╭─ Vehicle (choose one) ────────────────────────────────────────╮ │ --car [default: False] │ │ --truck [default: False] │ ╰───────────────────────────────────────────────────────────────╯ ╭─ Wheels ──────────────────────────────────────────────────────╮ │ --wheel-diameter [default: 18] │ │ --rims --no-rims [default: False] │ ╰───────────────────────────────────────────────────────────────╯ $ python my-script.py create --car --truck ╭─ Error ───────────────────────────────────────────────────────╮ │ Invalid values for group "Vehicle (choose one)". Mutually │ │ exclusive arguments: {--car, --truck} │ ╰───────────────────────────────────────────────────────────────╯ In this example, we use the :class:`~.validators.MutuallyExclusive` validator to make it so the user can only specify ``--car`` or ``--truck``. The default groups are defined by the registering app: * :attr:`.App.group_arguments` for positional-only arguments, which defaults to a group named ``"Arguments"``. * :attr:`.App.group_parameters` for all other parameters, which defaults to a group named ``"Parameters"``. ---------- Validators ---------- Group validators offer a way of jointly validating group parameter members of CLI-provided values. Groups with an empty name, or with ``show=False``, are a way of using group validators without impacting the help-page. .. code-block:: python from cyclopts import App, Group, Parameter, validators from typing import Annotated app = App() mutually_exclusive = Group( # This Group has no name, so it won't impact the help page. validator=validators.MutuallyExclusive(), # show_default=False - Showing "[default: False]" isn't too meaningful for mutually-exclusive options. # negative="" - Don't create a "--no-" flag default_parameter=Parameter(show_default=False, negative=""), ) @app.command def foo( car: Annotated[bool, Parameter(group=(app.group_parameters, mutually_exclusive))] = False, truck: Annotated[bool, Parameter(group=(app.group_parameters, mutually_exclusive))] = False, ): print(f"{car=} {truck=}") app() .. code-block:: console $ python demo.py foo --help Usage: demo.py foo [ARGS] [OPTIONS] ╭─ Parameters ──────────────────────────────────────────────────────╮ │ CAR,--car │ │ TRUCK,--truck │ ╰───────────────────────────────────────────────────────────────────╯ $ python demo.py foo --car car=True truck=False $ python demo.py foo --truck car=False truck=True $ python demo.py foo --car --truck ╭─ Error ───────────────────────────────────────────────────────────╮ │ Mutually exclusive arguments: {--car, --truck} │ ╰───────────────────────────────────────────────────────────────────╯ See :attr:`.Group.validator` for details. Cyclopts has some :ref:`builtin group-validators for common use-cases.` --------- Help Page --------- Groups form titled panels on the help-page. Groups with an empty name, or with :attr:`show=False <.Group.show>`, are **not** shown on the help-page. This is useful for applying additional grouping logic (such as applying a :class:`.LimitedChoice` validator) without impacting the help-page. By default, the ordering of panels is **alphabetical**. However, the sorting can be manipulated by :attr:`.Group.sort_key`. See it's documentation for usage. The :meth:`.Group.create_ordered` convenience classmethod creates a :class:`.Group` with a :attr:`~.Group.sort_key` value drawn drawn from a global monotonically increasing counter. This means that the order in the help-page will match the order that the groups were instantiated. .. code-block:: python from cyclopts import App, Group app = App() plants = Group.create_ordered("Plants") animals = Group.create_ordered("Animals") fungi = Group.create_ordered("Fungi") @app.command(group=animals) def zebra(): pass @app.command(group=plants) def daisy(): pass @app.command(group=fungi) def portobello(): pass app() .. code-block:: console $ my-script --help Usage: scratch.py COMMAND ╭─ Plants ───────────────────────────────────────────────────────────╮ │ daisy │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Animals ──────────────────────────────────────────────────────────╮ │ zebra │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Fungi ────────────────────────────────────────────────────────────╮ │ portobello │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ Even when using :meth:`.Group.create_ordered`, a :attr:`~.Group.sort_key` can still be supplied; the global counter will only be used to break sorting ties. cyclopts-3.9.0/docs/source/help.rst000066400000000000000000000241651475451620500173160ustar00rootroot00000000000000==== Help ==== A help screen is standard for every CLI application. Cyclopts by-default adds ``--help`` and ``-h`` flags to the application: .. code-block:: console $ my-application --help Usage: my-application COMMAND My application short description. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo Foo help string. │ │ bar Bar help string. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ Cyclopts derives the components of the help string from a variety of sources. The source resolution order is as follows (as applicable): 1. The ``help`` field in the :meth:`@app.command ` decorator. .. code-block:: python app = cyclopts.App() @app.command(help="This is the highest precedence help-string for 'bar'.") def bar(): pass When registering an :class:`.App` object, supplying ``help`` via the :meth:`@app.command ` decorator is forbidden to reduce ambiguity and will raise a :exc:`ValueError`. See (2). 2. Via :attr:`.App.help`. .. code-block:: python app = cyclopts.App(help="This help string has highest precedence at the app-level.") sub_app = cyclopts.App(help="This is the help string for the 'foo' subcommand.") app.command(sub_app, name="foo") app.command(sub_app, name="foo", help="This is illegal and raises a ValueError.") 3. The ``__doc__`` docstring of the registered :meth:`@app.default ` command. Cyclopts parses the docstring to populate short-descriptions and long-descriptions at the command-level, as well as at the parameter-level. .. code-block:: python app = cyclopts.App() app.command(cyclopts.App(), name="foo") @app.default def bar(val1: str): """This is the primary application docstring. Parameters ---------- val1: str This will be parsed for val1 help-string. """ @app["foo"].default # You can access sub-apps like a dictionary. def foo_handler(): """This will be shown for the "foo" command.""" 4. This resolution order, but of the :ref:`Meta App`. .. code-block:: python app = cyclopts.App() @app.meta.default def bar(): """This is the primary application docstring.""" ------------- Markup Format ------------- The standard markup language for docstrings in python is reStructuredText (see `PEP-0287`_). By default, Cyclopts parses docstring descriptions as restructuredtext and renders it appropriately. To change the markup format, set the :attr:`.App.help_format` field accordingly. Subapps inherit their parent's :attr:`.App.help_format` unless explicitly overridden. I.e. you only need to set :attr:`.App.help_format` in your main root application for all docstrings to be parsed appropriately. ^^^^^^^^^ PlainText ^^^^^^^^^ Do not perform any additional parsing, display supplied text as-is. .. code-block:: python from cyclopts import App app = App(help_format="plaintext") @app.default def default(): """My application summary. This is a pretty standard docstring; if there's a really long sentence I should probably wrap it because people don't like code that is more than 80 columns long. In this new paragraph, I would like to discuss the benefits of relaxing 80 cols to 120 cols. More text in this paragraph. Some new paragraph. """ app() .. code-block:: text Usage: default COMMAND My application summary. This is a pretty standard docstring; if there's a really long sentence I should probably wrap it because people don't like code that is more than 80 columns long. In this new paragraph, I would like to discuss the benefits of relaxing 80 cols to 120 cols. More text in this paragraph. Some new paragraph. ╭─ Commands ─────────────────────────────────────────────────────╮ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────╯ Most noteworthy, is **no additional text reflow is performed**; newlines are presented as-is. ^^^^ Rich ^^^^ Displays text as `Rich Markup`_. .. note:: Newlines are interpreted literally. .. code-block:: python from cyclopts import App app = App(help_format="rich") @app.default def default(): """Rich can display colors like [red]red[/red] easily. However, I cannot be bothered to figure out how to show that in documentation. """ app() .. raw:: html

^^^^^^^^^^^^^^^^ ReStructuredText ^^^^^^^^^^^^^^^^ ReStructuredText is the default parsing behavior of Cyclopts, so `help_format` won't need to be explicitly set. .. code-block:: python app = App(help_format="restructuredtext") # or "rst" # or don't supply help_format at all; rst is default. @app.default def default(): """My application summary. We can do RST things like have **bold text**. More words in this paragraph. This is a new paragraph with some bulletpoints below: * bullet point 1. * bullet point 2. """ app() Resulting help: .. raw:: html
Usage: default COMMAND

   My application summary.

   We can do RST things like have bold text. More words in this
   paragraph.

   This is a new paragraph with some bulletpoints below:

   1. bullet point 1.
   2. bullet point 2.

   ╭─ Commands ──────────────────────────────────────────────────────────╮
   │ --help -h  Display this message and exit.                           │
   │ --version  Display application version.                             │
   ╰─────────────────────────────────────────────────────────────────────╯
   
Under most circumstances, plaintext (without any additional markup) looks prettier and reflows better when interpreted as restructuredtext (or markdown, for that matter). ^^^^^^^^^ Markdown ^^^^^^^^^ Markdown is another popular markup language that Cyclopts can render. .. code-block:: python app = App(help_format="markdown") # or "md" @app.default def default(): """My application summary. We can do markdown things like have **bold text**. [Hyperlinks work as well.](https://cyclopts.readthedocs.io) """ Resulting help: .. raw:: html
Usage: default COMMAND

   My application summary.

   We can do markdown things like have bold text. Hyperlinks work as well.

   ╭─ Commands ──────────────────────────────────────────────────────────╮
   │ --help -h  Display this message and exit.                           │
   │ --version  Display application version.                             │
   ╰─────────────────────────────────────────────────────────────────────╯
   
---------- Help Flags ---------- The default ``--help`` flags can be changed to different name(s) via the ``help_flags`` parameter. .. code-block:: python app = cyclopts.App(help_flags="--show-help") app = cyclopts.App(help_flags=["--send-help", "--send-help-plz", "-h"]) To disable the help-page entirely, set ``help_flags`` to an empty string or iterable. .. code-block:: python app = cyclopts.App(help_flags="") app = cyclopts.App(help_flags=[]) .. _PEP-0257: https://peps.python.org/pep-0257/ .. _PEP-0287: https://peps.python.org/pep-0287/ .. _Rich Markup: https://rich.readthedocs.io/en/stable/markup.html cyclopts-3.9.0/docs/source/index.rst000066400000000000000000000021441475451620500174660ustar00rootroot00000000000000======== Cyclopts ======== .. include:: ../../README.md :parser: myst_parser.sphinx_ For extensive documentation on all the features Cyclopts has to offer, checkout the :ref:`API` page. .. toctree:: :maxdepth: 1 :caption: Usage Installation.rst getting_started.rst commands.rst parameters.rst default_parameter.rst groups.rst parameter_validators.rst group_validators.rst help.rst version.rst rules.rst text_editor.rst api .. toctree:: :maxdepth: 2 :caption: Advanced Usage user_classes.rst args_and_kwargs.rst config_file.rst packaging.rst app_calling.rst meta_app.rst command_chaining.rst autoregistry .. toctree:: :maxdepth: 2 :caption: Cookbook cookbook/app_upgrade.rst cookbook/interactive_help.rst cookbook/rich_formatted_exceptions.rst cookbook/sharing_parameters.rst cookbook/unit_testing.rst .. toctree:: :maxdepth: 2 :caption: Migration migration/typer.rst .. toctree:: :maxdepth: 2 :caption: Alternative Libraries vs_typer/README.rst vs_fire/README.rst vs_arguably/README.rst cyclopts-3.9.0/docs/source/meta_app.rst000066400000000000000000000153771475451620500201610ustar00rootroot00000000000000.. _Meta App: ======== Meta App ======== What if you want more control over the application launch process? Cyclopts provides the option of launching an app from an app; a meta app! ------------ Meta Sub App ------------ Typically, a Cyclopts application is launched by calling the :class:`.App` object: .. code-block:: python from cyclopts import App app = App() # Register some commands here (not shown) app() # Run the app To change how the primary app is run, you can use the meta-app feature of Cyclopts. The meta app is just like a normal Cyclopts :class:`.App`, the only thing special about it is that it's help-page gets merged in with it's parenting app. .. code-block:: python from cyclopts import App, Group, Parameter from typing import Annotated app = App() # Rename the meta's "Parameter" -> "Session Parameters". # Set sort_key so it will be drawn higher up the help-page. app.meta.group_parameters = Group("Session Parameters", sort_key=0) @app.command def foo(loops: int): for i in range(loops): print(f"Looping! {i}") @app.meta.default def my_app_launcher(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], user: str): print(f"Hello {user}") app(tokens) app.meta() .. code-block:: console $ my-script --user=Bob foo 3 Hello Bob Looping! 0 Looping! 1 Looping! 2 The variable positional ``*tokens`` will aggregate all remaining tokens, including those starting with a hyphen (typically options). We can then pass them along to the primary ``app``. The ``meta`` app is mostly a normal Cyclopts app; the only thing special about it is that it will be additionally scanned when generate help screens ``*tokens`` is annotated with ``show=False`` since we do not want this variable to show up in the help screen. .. code-block:: console $ my-script --help Usage: my-script COMMAND ╭─ Session Parameters ────────────────────────────────────────────────────╮ │ * --user [required] │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ──────────────────────────────────────────────────────────────╮ │ foo │ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────────╯ ------------- Meta Commands ------------- If you want a command to circumvent ``my_app_launcher``, add it as you would any other command to the meta app. .. code-block:: python @app.meta.command def info(): print("CLI didn't have to provide --user to call this.") .. code-block:: console $ my-script info CLI didn't have to provide --user to call this. $ my-script --help Usage: my-script COMMAND ╭─ Session Parameters ────────────────────────────────────────────────────╮ │ * --user [required] │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ──────────────────────────────────────────────────────────────╮ │ foo │ │ info │ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────────╯ Just like a standard application, the parsed ``command`` executes instead of ``default``. ------------------------- Custom Command Invocation ------------------------- The core logic of :meth:`App.__call__` method is the following: .. code-block:: python def __call__(self, tokens=None, **kwargs): command, bound, ignored = self.parse_args(tokens, **kwargs) return command(*bound.args, **bound.kwargs) Knowing this, we can easily customize how we actually invoke actions with Cyclopts. Let's imagine that we want to instantiate an object, ``User`` in our meta app, and pass it to subsequent commands that need it. This might be useful to share an expensive-to-create object amongst commands in a single session; see :ref:`Command Chaining`. .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() class User: def __init__(self, name): self.name = name @app.command def create( age: int, *, user_obj: Annotated[User, Parameter(parse=False)], ): print(f"Creating user {user_obj.name} with age {age}.") @app.meta.default def launcher(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], user: str): additional_kwargs = {} command, bound, ignored = app.parse_args(tokens) # "ignored" is a dict mapping python-variable-name to it's type annotation for parameters with "parse=False". if "user_obj" in ignored: # 'ignored["user_obj"]' is the class "User" additional_kwargs["user_obj"] = ignored["user_obj"](user) return command(*bound.args, **bound.kwargs, **additional_kwargs) if __name__ == "__main__": app.meta() .. code-block:: console $ my-script create --user Alice 30 Creating user Alice with age 30. The ``parse=False`` configuration tells Cyclopts to not try and bind arguments to this parameter. Cyclopts will pass it along to ``ignored`` to make custom meta-app logic easier. The annotated parameter **must** be a keyword-only parameter. cyclopts-3.9.0/docs/source/migration/000077500000000000000000000000001475451620500176155ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/migration/typer.rst000066400000000000000000000111051475451620500215100ustar00rootroot00000000000000==================== Migrating From Typer ==================== Much of Cyclopts's syntax is `Typer`_-inspired. Migrating from Typer should be pretty straightforward; it is recommended to first read the :ref:`Getting Started` and :ref:`Commands` sections. The below table offers a jumping off point for translating the various portions of the APIs. The :ref:`Typer Comparison` page also provides many examples comparing the APIs. .. list-table:: Typer-to-Cyclopts API Reference :widths: 30 30 40 :header-rows: 1 * - Typer - Cyclopts - Notes * - :class:`typer.Typer()` - :class:`cyclopts.App()` - Same/similar fields: + :attr:`.App.name` - Optional name of application or sub-command. Cyclopts has more user-friendly default features: + Equivalent ``no_args_is_help=True``. + Equivalent ``pretty_exceptions_enable=False``. * - :meth:`@app.command()` - :meth:`@app.command() <.App.command>` - In Cyclopts, ``@app.command`` :ref:`always results in a command. ` To define an action when no command is provided, see :meth:`@app.default <.App.default>`. * - :meth:`app.add_typer(...)` - :meth:`app.command(...)` - Sub applications and commands are registered the same way in Cyclopts. * - :meth:`@app.callback()` - :meth:`@app.default() <.App.default>` :meth:`@app.meta.default() <.App.default>` - Typer's callback always executes before executing an app. If used to provide functionality when no command was specified from the CLI, then use :meth:`@app.default() <.App.default>`. Otherwise, checkout Cyclopt's :ref:`Meta App`. * - :class:`Annotated[..., typer.Argument(...)]` :class:`Annotated[..., typer.Option(...)]` - :class:`Annotated[..., cyclopts.Parameter(...)] <.Parameter>` - In Cyclopts, Positional/Keyword arguments :ref:`are determined from the function signature. ` Some of Typer's validation fields, like ``exists`` for :class:`~pathlib.Path` types are handled in Cyclopts :ref:`by explicit validators. ` Cyclopts and Typer mostly handle type-hints the same way, but there are a few notable exceptions: .. list-table:: Typer-to-Cyclopts Type-Hints :widths: 30 70 :header-rows: 1 * - Type Annotation - Notes * - :class:`~enum.Enum` - Compared to Typer, Cyclopts handles :class:`~enum.Enum` lookups :ref:`in the reverse direction. ` Frequently, :obj:`~typing.Literal` :ref:`offers a more terse, intuitive choice option. ` * - :obj:`~typing.Union` - Typer does **not** support type unions. :ref:`Cyclopts does. ` ------------- General Steps ------------- #. Add the following import: ``from cyclopts import App, Parameter``. #. Change ``app = Typer(...)`` to just ``app = App()``. Revisit more advanced configuration later. #. Remove all ``@app.callback`` stuff. Cyclopts already provides a good ``--version`` handler for you. #. Replace all ``Annotated[..., Argument/Option]`` type-hints with :class:`Annotated[..., Parameter()] <.Parameter>`. If only supplying a :attr:`~.Parameter.help` string, :ref:`it's better to supply it via docstring. ` #. Cyclopts has similar boolean-flag handling as Typer, :ref:`but has different configuration parameters. ` .. code-block:: python ######### # Typer # ######### # Overriding the name results in no "False" flag generation. my_flag: Annotated[bool, Option("--my-custom-flag")] # However, it can be custom specified: my_flag: Annotated[bool, Option("--my-custom-flag/--disable-my-custom-flag")] ############ # Cyclopts # ############ # Overriding the name still results in "False" flag generation: # --my-custom-flag --no-my-custom-flag my_flag: Annotated[bool, Parameter("--my-custom-flag")] # Negative flag generation can be disabled: # --my-custom-flag my_flag: Annotated[bool, Parameter("--my-custom-flag", negative="")] # Or the prefix can be changed: # --my-custom-flag --disable-my-custom-flag my_flag: Annotated[bool, Parameter("--my-custom-flag", negative_bool="--disable-")] After the basic migration is done, it is recommended to read through the rest of Cyclopts's documentation to learn about some of the better functionality it has, which could result in cleaner, terser code. .. _Typer: https://typer.tiangolo.com .. _always results in a command.: https://github.com/tiangolo/typer/issues/315 cyclopts-3.9.0/docs/source/packaging.rst000066400000000000000000000063271475451620500203120ustar00rootroot00000000000000========= Packaging ========= Packaging is bundling up your python library so that it can be easily ``pip install`` by others. Typically this involves: 1. Bundling the code into a Built Distribution (wheel) and/or Source Distribution (sdist). 2. Uploading (publishing) the distribution(s) to python package repository, like PyPI. This section is a brief bootcamp on package **configuration** for a CLI application. This is **not** intended to be a complete tutorial on python packaging and publishing. In this tutorial, replace all instances of ``mypackage`` with your own project name. --------------- \_\_main\_\_.py --------------- In python, if you have a module ``mypackage/__main__.py``, it will be executed with the bash command ``python -m mypackage``. A pretty bare-bones Cyclopts ``mypackage/__main__.py`` will look like: .. code-block:: python # mypackage/__main__.py import cyclopts app = cyclopts.App() @app.command def foo(name: str): print(f"Hello {name}!") def main(): app() if __name__ == "__main__": main() .. code-block:: console $ python -m mypackage World Hello World! In the current state, the :func:`main` function is an unnecessary extra level of indirection (could just directly call :obj:`app`), but it can sometimes offer you additional flexibility in the future if you need it. ----------- Entrypoints ----------- If you want your application to be callable like a standard bash executable (i.e. ``my-package`` instead of ``python -m mypackage``), we'll need to add an entrypoint_. We'll investigate the setuptools solution, and the poetry solution. ^^^^^^^^^^ Setuptools ^^^^^^^^^^ ``setup.py`` is a script at the root of your project that gets executed upon installation. ``setup.cfg`` and ``pyproject.toml`` are two other alternatives that are supported. The following are all equivalent, **but should not be used at the same time**. It is important that the function specified **takes no arguments**. .. code-block:: python # setup.py from setuptools import setup setup( # There should be a lot more fields populated here. entry_points={ "console_scripts": [ "my-package = mypackage.__main__:main", ] }, ) .. code-block:: toml # pyproject.toml [project.scripts] my-package = "mypackage.__main__:main" .. code-block:: cfg # setup.cfg [options.entry_points] console_scripts = my-package = mypackage.__main__:main All of these represent the same thing: create an executable named ``my-package`` that executes function ``main`` (from the right of the colon) from the python module ``mypackage.__main__``. Note that this configuration is independent of any special naming, like ``__main__`` or ``main``. The setuptools entrypoint_ documentation goes into further detail. ^^^^^^ Poetry ^^^^^^ Poetry_ is a tool for dependency management and packaging in Python (and what Cyclopts uses). The syntax is very similar to setuptools: .. code-block:: toml # pyproject.toml [tool.poetry.scripts] my-package = "mypackage.__main__:main" .. _Poetry: https://python-poetry.org .. _entrypoint: https://setuptools.pypa.io/en/latest/userguide/entry_point.html#entry-points cyclopts-3.9.0/docs/source/parameter_validators.rst000066400000000000000000000072671475451620500226020ustar00rootroot00000000000000.. _Parameter Validators: ==================== Parameter Validators ==================== In CLI applications, users have the freedom to input a wide range of data. This flexibility can lead to inputs the application does not expect. By coercing the input into a data type (like an :obj:`int`), we are already limiting the input to a certain degree (e.g. "foo" cannot be coerced into an integer). To further restrict the user input, you can populate the :attr:`~.Parameter.validator` field of :class:`.Parameter`. A validator is any callable object (such as a function) that has the signature: .. code-block:: python def validator(type_, value: Any) -> None: pass # Raise any exception here if ``value`` is invalid. Validation happens **after** the data converter runs. Any of :exc:`AssertionError`, :exc:`TypeError` or :exc:`ValidationError` will be promoted to a :exc:`cyclopts.ValidationError` so that the exception gets presented to the end-user in a nicer way. More than one validator can be supplied as a list to the :attr:`~.Parameter.validator` field. Cyclopts has some builtin common validators in the :ref:`cyclopts.validators ` module. See :ref:`Annotated Types` for common specific definitions provided as convenient pre-annotated types. ---- Path ---- The :class:`.Path` validator ensures certain properties of the parsed :class:`pathlib.Path` object, such as asserting the file must exist. .. code-block:: python from cyclopts import App, Parameter, validators from typing import Annotated from pathlib import Path app = App() @app.default() def foo(path: Annotated[Path, Parameter(validator=validators.Path(exists=True))]): print(f"File contents:\n{path.read_text()}") app() .. code-block:: console $ echo Hello World > my_file.txt $ my-script my_file.txt File contents: Hello World $ my-script this_file_does_not_exist.txt ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid value "this_file_does_not_exist.txt" for "PATH". │ │ "this_file_does_not_exist.txt" does not exist. │ ╰────────────────────────────────────────────────────────────────────╯ See :ref:`Annotated Path Types ` for Annotated-Type equivalents of common Path converter/validators. ------ Number ------ The :class:`.Number` validator can set minimum and maximum input values. .. code-block:: python from cyclopts import App, Parameter, validators from typing import Annotated app = App() @app.default() def foo(n: Annotated[int, Parameter(validator=validators.Number(gte=0, lt=16))]): print(f"Your number in hex is {str(hex(n))[2]}.") app() .. code-block:: console $ my-script 0 Your number in hex is 0. $ my-script 15 Your number in hex is f. $ my-script 16 ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid value "16" for "N". Must be < 16. │ ╰────────────────────────────────────────────────────────────────────╯ See :ref:`Annotated Number Types ` for Annotated-Type equivalents of common Number converter/validators. cyclopts-3.9.0/docs/source/parameters.rst000066400000000000000000000356051475451620500205320ustar00rootroot00000000000000========== Parameters ========== Typically, Cyclopts gets all the information it needs from object names, type hints, and the function docstring: .. code-block:: python from cyclopts import App app = App(help="This is help for the root application.") @app.command def foo(value: int): # Cyclopts uses the ``value`` name and ``int`` type hint """Cyclopts uses this short description for help. Parameters ---------- value: int Cyclopts uses this description for ``value``'s help. """ app() Running the example: .. code-block:: console $ my-script --help Usage: my-script COMMAND This is help for the root application. ╭─ Commands ──────────────────────────────────────────────────────────╮ │ foo Cyclopts uses this short description for help. │ │ --help,-h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────╯ $ my-script foo --help Usage: my-script [ARGS] [OPTIONS] Cyclopts uses this short description for help. ╭─ Parameters ─────────────────────────────────────────────────────────────────────────╮ │ * VALUE --value Cyclopts uses this description for value's help. [required] │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ This keeps the code as clean and terse as possible. However, if more control is required, we can provide additional information by `annotating `_ type hints with :class:`.Parameter`. .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() @app.command def foo(bar: Annotated[int, Parameter(...)]): pass app() :class:`.Parameter` gives complete control on how Cyclopts processes the annotated parameter. See the :ref:`API` page for all configurable options. This page will investigate some of the more common use-cases. .. note:: :class:`.Parameter` can also be used as a decorator. This is :ref:`particularly useful for class definitions `. ------ Naming ------ Like :ref:`command names `, CLI parameter names are derived from their python counterparts. However, sometimes customization is needed. .. _Parameters - Naming - Manual Naming: ^^^^^^^^^^^^^ Manual Naming ^^^^^^^^^^^^^ Parameter names (and their short forms) can be manually specified: .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() @app.default def main( *, foo: Annotated[str, Parameter(name=["--foo", "-f"])], # Adding a short-form bar: Annotated[str, Parameter(name="--something-else")], ): pass app() .. code-block:: console $ my-script --help Usage: main COMMAND [OPTIONS] ╭─ Commands ──────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────╮ │ * --foo -f [required] │ │ * --something-else [required] │ ╰─────────────────────────────────────────────────────────╯ Manually set names via :attr:`Parameter.name ` are not subject to :attr:`Parameter.name_transform `. ^^^^^^^^^^^^^^ Name Transform ^^^^^^^^^^^^^^ The name transform function that converts the python variable name to it's CLI counterpart can be configured by setting :attr:`Parameter.name_transform ` (defaults to :func:`.default_name_transform`). .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() def name_transform(s: str) -> str: return s.upper() @app.default def main( *, foo: Annotated[str, Parameter(name_transform=name_transform)], bar: Annotated[str, Parameter(name_transform=name_transform)], ): pass app() .. code-block:: console $ my-script --help Usage: main COMMAND [OPTIONS] ╭─ Commands ──────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────╮ │ * --FOO [required] │ │ * --BAR [required] │ ╰─────────────────────────────────────────────────────────╯ Notice how the parameter is now ``--FOO`` instead of the standard ``--foo``. .. note:: The returned string is **before** the standard ``--`` is prepended. Generally, it is not very useful to set the name transform on **individual** parameters; it would be easier/clearer :ref:`to manually specify the name `. However, we can change the default name transform for the **entire app** by configuring the app's :ref:`default_parameter `. To change the :attr:`~cyclopts.Parameter.name_transform` across your entire app, add the following to your :class:`~cyclopts.App` configuration: .. code-block:: python app = App( default_parameter=Parameter(name_transform=my_custom_name_transform), ) ---- Help ---- It is recommended to use docstrings for your parameter help, but if necessary, you can explicitly set a help string: .. code-block:: python @app.command def foo(value: Annotated[int, Parameter(help="THIS IS USED.")]): """ Parameters ---------- value: int This description is not used; got overridden. """ .. code-block:: console $ my-script foo --help ╭─ Parameters ──────────────────────────────────────────────────╮ │ * VALUE,--value THIS IS USED. [required] │ ╰───────────────────────────────────────────────────────────────╯ .. _Converters: ---------- Converters ---------- Cyclopts has a powerful coercion engine that automatically converts CLI string tokens to the types hinted in a function signature. However, sometimes a custom :attr:`~.Parameter.converter` is required. Lets consider a case where we want the user to specify a file size, and we want to allows suffixes like `"MB"`. .. code-block:: python from cyclopts import App, Parameter, Token from typing import Annotated, Sequence from pathlib import Path app = App() mapping = { "kb": 1024, "mb": 1024 * 1024, "gb": 1024 * 1024 * 1024, } def byte_units(type_, tokens: Sequence[Token]) -> int: # type_ is ``int``, value = tokens[0].value.lower() try: return type_(value) # If this works, it didn't have a suffix. except ValueError: pass number, suffix = value[:-2], value[-2:] return int(number) * mapping[suffix] @app.command def zero(file: Path, size: Annotated[int, Parameter(converter=byte_units)]): """Creates a file of all-zeros.""" print(f"Writing {size} zeros to {file}.") file.write_bytes(bytes(size)) app() .. code-block:: console $ my-script zero out.bin 100 Writing 100 zeros to out.bin. $ my-script zero out.bin 1kb Writing 1024 zeros to out.bin. $ my-script zero out.bin 3mb Writing 3145728 zeros to out.bin. The converter function gets the annotated type, and the :class:`.Token` s parsed for this argument. Tokens are Cyclopt's way of bookkeeping user inputs; in the last command the ``tokens`` object would look like: .. code-block:: python # tokens is a length-1 tuple. The variable "size" only takes in 1 token: tuple( Token( keyword=None, # "3mb" was provided positionally, not by keyword value='3mb', # The string from the command line source='cli', # The value came from the command line, as opposed to other Cyclopts mechanisms. index=0, # For the variable "size", this is the first (0th) token. ), ) ---------------- Validating Input ---------------- Just because data is of the correct type, doesn't mean it's valid. If we had a program that accepts integer user age as an input, ``-1`` is an integer, but not a valid age. .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() def validate_age(type_, value): if value < 0: raise ValueError("Negative ages not allowed.") if value > 150: raise ValueError("You are too old to be using this application.") @app.default def allowed_to_buy_alcohol(age: Annotated[int, Parameter(validator=validate_age)]): print("Under 21: prohibited." if age < 21 else "Good to go!") app() .. code-block:: console $ my-script 30 Good to go! $ my-script 10 Under 21: prohibited. $ my-script -1 ╭─ Error ──────────────────────────────────────────────────────────────────────╮ │ Invalid value "-1" for "AGE". Negative ages not allowed. │ ╰──────────────────────────────────────────────────────────────────────────────╯ $ my-script 200 ╭─ Error ──────────────────────────────────────────────────────────────────────╮ │ Invalid value "200" for "AGE". You are too old to be using this application. │ ╰──────────────────────────────────────────────────────────────────────────────╯ Certain builtin error types (:exc:`ValueError`, :exc:`TypeError`, :exc:`AssertionError`) will be re-interpreted by Cyclopts and formatted into a prettier message for the application user. Cyclopts has some :ref:`builtin validators ` for common situations We can create a similar app as above: .. code-block:: python from cyclopts import App, Parameter, validators from typing import Annotated app = App() @app.default def allowed_to_buy_alcohol(age: Annotated[int, Parameter(validator=validators.Number(gte=0, lte=150))]): # gte - greater than or equal to # lte - less than or equal to print("Under 21: prohibited." if age < 21 else "Good to go!") app() Taking this one step further, Cyclopts has some :ref:`builtin convenience types `. If we didn't care about the upper age bound, we could simplify the application to: .. code-block:: python from cyclopts import App from cyclopts.types import NonNegativeInt app = App() @app.default def allowed_to_buy_alcohol(age: NonNegativeInt): print("Under 21: prohibited." if age < 21 else "Good to go!") app() -------------------- Parameter Resolution -------------------- Cyclopts can combine multiple :class:`.Parameter` annotations together. Say you want to define a new :obj:`int` type that uses the :ref:`byte-centric converter from above`. We can define the type: .. code-block:: python ByteSize = Annotated[int, Parameter(converter=byte_units)] We can then either directly annotate a function parameter with this: .. code-block:: python @app.command def zero(size: ByteSize): pass or even stack annotations to add additional features, like a validator: .. code-block:: python def must_be_multiple_of_4096(type_, value): assert value % 4096 == 0, "Size must be a multiple of 4096" @app.command def zero(size: Annotated[ByteSize, Parameter(validator=must_be_multiple_of_4096)]): pass Python automatically flattens out annotations, so this is interpreted as: .. code-block:: python Annotated[ByteSize, Parameter(converter=byte_units), Parameter(validator=must_be_multiple_of_4096)] Cyclopts will search **right-to-left** for **set** parameter attributes until one is found. I.e. right-most parameter attributes have the highest priority. .. code-block:: console $ my-script 1234 ╭─ Error ──────────────────────────────────────────────────────────────────────╮ │ Invalid value "1234" for "SIZE". Size must be a multiple of 4096 │ ╰──────────────────────────────────────────────────────────────────────────────╯ See :ref:`Parameter Resolution Order` for more details. cyclopts-3.9.0/docs/source/rules.rst000066400000000000000000000670521475451620500175220ustar00rootroot00000000000000.. _Coercion Rules: ============== Coercion Rules ============== This page intends to serve as a terse set of type coercion rules that Cyclopts follows. Automatic coercion can always be overridden by the :attr:`.Parameter.converter` field. Typically, the :attr:`~.Parameter.converter` function will receive a single token, but it may receive multiple tokens if the annotated type is iterable (e.g. :class:`list`, :class:`set`). ******* No Hint ******* If no explicit type hint is provided: * If the parameter has a **non-None** default value, interpret the type as ``type(default_value)``. .. code-block:: python from cyclopts import App app = App() @app.default def default(value=5): print(f"{value=} {type(value)=}") app() .. code-block:: console $ my-program 3 value=3 type(value)= * Otherwise, :ref:`interpret the type as string `. .. code-block:: python from cyclopts import App app = App() @app.default def default(value): print(f"{value=} {type(value)=}") app() .. code-block:: console $ my-program foo value='foo' type(value)= *** Any *** A standalone ``Any`` type hint is equivalent to `No Hint`_ .. _Coercion Rules - Str: *** Str *** No operation is performed, CLI tokens are natively strings. .. code-block:: python from cyclopts import App app = App() @app.default def default(value: str): print(f"{value=} {type(value)=}") app() .. code-block:: console $ my-program foo value='foo' type(value)= *** Int *** For convenience, Cyclopts provides a richer feature-set of parsing integers than just naively calling ``int``. * Accepts vanilla decimal values (e.g. `123`, `3.1415`). Floating-point values will be rounded prior to casting to an ``int``. * Accepts binary values (strings starting with `0b`) * Accepts octal values (strings starting with `0o`) * Accepts hexadecimal values (strings starting with `0x`). ***** Float ***** Token gets cast as ``float(token)``. For example, ``float("3.14")``. ******* Complex ******* Token gets cast as ``complex(token)``. For example, ``complex("3+5j")`` **** Bool **** 1. If specified as a **keyword**, booleans are interpreted flags that take no parameter. The default **false-like** flag are ``--no-FLAG-NAME``. See :attr:`.Parameter.negative` for more about this feature. Example: .. code-block:: python from cyclopts import App app = App() @app.command def foo(my_flag: bool): print(my_flag) app() .. code-block:: console $ my-program foo --my-flag True $ my-program foo --no-my-flag False 2. If specified as a **positional** argument, a case-insensitive lookup is performed: * If the token is a **true-like value** ``{"yes", "y", "1", "true", "t"}``, then it is parsed as :obj:`True`. * If the token is a **false-like value** ``{"no", "n", "0", "false", "f"}``, then it is parsed as :obj:`False`. * Otherwise, a :exc:`CoercionError` will be raised. .. code-block:: console $ my-program foo 1 True $ my-program foo 0 False $ my-program foo not-a-true-or-false-value ╭─ Error ─────────────────────────────────────────────────╮ │ Invalid value for "--my-flag": unable to convert │ │ "not-a-true-or-false-value" into bool. │ ╰─────────────────────────────────────────────────────────╯ 3. If specified as a keyword with a value attached with an ``=``, then the provided value will be parsed according to positional argument rules above (2). .. code-block:: python from cyclopts import App app = App() @app.command def foo(my_flag: bool): print(my_flag) app() .. code-block:: console $ my-program foo --my-flag=true True $ my-program foo --my-flag=false False $ my-program foo --no-my-flag=true False $ my-program foo --no-my-flag=false True **** List **** Unlike more simple types like :obj:`str` and :obj:`int`, lists use different parsing rules depending on if the values are provided positionally or by keyword. ^^^^^^^^^^ Positional ^^^^^^^^^^ When arguments are provided positionally: * If :attr:`Parameter.allow_leading_hyphen` is :obj:`False` (default behavior), reaching an option-like token will stop parsing for this parameter. If the number of consumed tokens is not a multiple of the required number of tokens to create an element of the list, a :exc:`MissingArgumentError` will be raised. .. code-block:: python from cyclopts import App app = App() @app.command def foo(values: list[int]): # 1 CLI token per element print(values) @app.command def bar(values: list[tuple[int, str]]): # 2 CLI tokens per element print(values) app() .. code-block:: console $ my-program foo 1 2 3 [1, 2, 3] $ my-program bar 1 one 2 two [(1, 'one'), (2, 'two')] $ my-program bar 1 one 2 ╭─ Error ─────────────────────────────────────────────────────╮ │ Command "bar" parameter "--values" requires 2 arguments. │ │ Only got 1. │ ╰─────────────────────────────────────────────────────────────╯ * If :attr:`Parameter.allow_leading_hyphen` is :obj:`True`, CLI tokens will be consumed unconditionally until exhausted. .. code-block:: python from cyclopts import App, Parameter from pathlib import Path from typing import Annotated app = App() @app.default def main( files: Annotated[list[Path], Parameter(allow_leading_hyphen=True)], some_flag: bool = False, ): print(f"{some_flag=}") print(f"Analyzing files {files}") app() .. code-block:: console $ my-program foo.bin bar.bin --fizz.bin buzz.bin --some-flag some_flag=True Analyzing files [PosixPath('foo.bin'), PosixPath('bar.bin'), PosixPath('--fizz.bin'), PosixPath('buzz.bin')] Known keyword arguments are parsed first (in this case, ``--some-flag``). To unambiguously pass in values positionally, provide them after a bare ``--``: .. code-block:: console $ my-program -- foo.bin bar.bin --fizz.bin buzz.bin --some-flag some_flag=False Analyzing files [PosixPath('foo.bin'), PosixPath('bar.bin'), PosixPath('--fizz.bin'), PosixPath('buzz.bin'), PosixPath('--some-flag')] ^^^^^^^ Keyword ^^^^^^^ When arguments are provided by keyword: * Tokens will be consumed until enough data is collected to form the type-hinted object. * The keyword can be specified multiple times. * If :attr:`Parameter.allow_leading_hyphen` is :obj:`False` (default behavior), reaching an option-like token will raise :exc:`MissingArgumentError` if insufficient tokens have been parsed. .. code-block:: python from cyclopts import App app = App() @app.command def foo(values: list[int]): # 1 CLI token per element print(values) @app.command def bar(values: list[tuple[int, str]]): # 2 CLI tokens per element print(values) app() .. code-block:: console $ my-program foo --values 1 --values 2 --values 3 [1, 2, 3] $ my-program bar --values 1 one --values 2 two [(1, 'one'), (2, 'two')] $ my-program bar --values 1 --values 2 ╭─ Error ─────────────────────────────────────────────────────╮ │ Command "bar" parameter "--values" requires 2 arguments. │ │ Only got 1. │ ╰─────────────────────────────────────────────────────────────╯ * If :attr:`Parameter.consume_multiple` is :obj:`True`, all remaining tokens will be consumed (until an option-like token is reached if :attr:`Parameter.allow_leading_hyphen` is :obj:`False`) .. code-block:: python from cyclopts import App, Parameter from typing import Annotated app = App() @app.default def foo(values: Annotated[list[int], Parameter(consume_multiple=True)]): # 1 CLI token per element print(values) app() .. code-block:: console $ my-program foo --values 1 2 3 [1, 2, 3] ^^^^^^^^^^ Empty List ^^^^^^^^^^ Commonly, if we want a default list for a parameter in a function, we set the default value to ``None`` in the signature and then set it to the actual list in the function body: .. code-block:: python def foo(extensions: Optional[list] = None): if extensions is None: extensions = [".png", ".jpg"] We do this because mutable defaults is a `common unexpected source of bugs in python `_. However, sometimes we actually want to specify an empty list. To get an empty list pass in the flag ``--empty-MY-LIST-NAME``. .. code-block:: from cyclopts import App app = App() @app.default def main(extensions: list | None = None): if extensions is None: extensions = [".png", ".jpg"] print(f"{extensions=}") app() .. code-block:: console $ my-program extensions=['.png', '.jpg'] $ my-program --empty-extensions extensions=[] See :attr:`.Parameter.negative` for more about this feature. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Positional Only With Subsequent Parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When a list is **positional-only**, it will consume tokens such that it leaves enough tokens for subsequent positional-only parameters. .. code-block:: python from pathlib import Path from cyclopts import App app = App() @app.default def main(srcs: list[Path], dst: Path, /): # "/" makes all prior parameters POSITIONAL_ONLY print(f"Processing files {srcs!r} to {dst!r}.") app() .. code-block:: console $ my-program foo.bin bar.bin output.bin Processing files [PosixPath('foo.bin'), PosixPath('bar.bin')] to PosixPath('output.bin'). The console wildcard ``*`` is expanded by the console, so this example will naturally work with wildcards. .. code-block:: console $ ls foo buzz.bin fizz.bin $ my-program foo/*.bin output.bin Processing files [PosixPath('foo/buzz.bin'), PosixPath('foo/fizz.bin')] to PosixPath('output.bin'). ******** Iterable ******** Follows the same rules as `List`_. The passed in data will be a :class:`list`. ******** Sequence ******** Follows the same rules as `List`_. The passed in data will be a :class:`list`. *** Set *** Follows the same rules as `List`_, but the resulting datatype is a :class:`set`. ********* Frozenset ********* Follows the same rules as `Set`_, but the resulting datatype is a :class:`frozenset`. ***** Tuple ***** * The inner type hint(s) will be applied independently to each element. Enough CLI tokens will be consumed to populate the inner types. * Nested fixed-length tuples are allowed: E.g. ``tuple[tuple[int, str], str]`` will consume 3 CLI tokens. * Indeterminite-size tuples ``tuple[type, ...]`` are only supported at the root-annotation level and behave similarly to `List`_. .. code-block:: python from cyclopts import App app = App() @app.default def default(coordinates: tuple[float, float, str]): print(f"{coordinates=}") app() And invoke our script: .. code-block:: console $ my-program --coordinates 3.14 2.718 my-coord-name coordinates=(3.14, 2.718, 'my-coord-name') .. _Coercion Rules - Union: **** Dict **** Cyclopts can populate dictionaries using keyword dot-notation: .. code-block:: python from cyclopts import App app = App() @app.default def default(message: str, *, mapping: dict[str, str] | None = None): if mapping: for find, replace in mapping.items(): message = message.replace(find, replace) print(message) app() .. code-block:: console $ my_program 'Hello Cyclopts users!' Hello Cyclopts users! $ my_program 'Hello Cyclopts users!' --mapping.Hello Hey Hey Cyclopts users! $ my_program 'Hello Cyclopts users!' --mapping.Hello Hey --mapping.users developers Hey Cyclopts developers! Due to the way of specifying keys, it is recommended to make dict parameters keyword-only; dicts **cannot** be populated positionally. If you do not wish for the user to be able to specify arbitrary keys, see `User-Defined Classes`_. For specifying arbitrary keywords at the root level, see :ref:`kwargs `. ***** Union ***** The unioned types will be iterated **left-to-right** until a successful coercion is performed. :obj:`None` type hints are ignored. .. code-block:: python from cyclopts import App from typing import Union app = App() @app.default def default(a: Union[None, int, str]): print(type(a)) app() .. code-block:: console $ my-program 10 $ my-program bar ******** Optional ******** ``Optional[...]`` is syntactic sugar for ``Union[..., None]``. See Union_ rules. .. _Coercion Rules - Literal: ******* Literal ******* The :obj:`~typing.Literal` type is a good option for limiting user input to a set of choices. Like Union_, the :obj:`~typing.Literal` options will be iterated **left-to-right** until a successful coercion is performed. Cyclopts attempts to coerce the input token into the **type** of each :obj:`~typing.Literal` option. .. code-block:: python from cyclopts import App from typing import Literal app = App() @app.default def default(value: Literal["foo", "bar", 3]): print(f"{value=} {type(value)=}") app() .. code-block:: console $ my-program foo value='foo' type(value)= $ my-program bar value='bar' type(value)= $ my-program 3 value=3 type(value)= $ my-program fizz ╭─ Error ─────────────────────────────────────────────────╮ │ Invalid value for "VALUE": unable to convert "fizz" │ │ into one of {'foo', 'bar', 3}. │ ╰─────────────────────────────────────────────────────────╯ **** Enum **** While `Literal`_ is the recommended way of providing the user a set of choices, another method is using :class:`~enum.Enum`. The :attr:`Parameter.name_transform ` gets applied to all :class:`~enum.Enum` names, as well as the CLI provided token. By default,this means that a **case-insensitive name** lookup is performed. If an enum name contains an underscore, the CLI parameter **may** instead contain a hyphen, ``-``. Leading/Trailing underscores will be stripped. If coming from Typer_, **Cyclopts Enum handling is the reverse of Typer**. Typer attempts to match the token to an Enum **value**; Cyclopts attempts to match the token to an Enum **name**. This is done because generally the **name** of the enum is meant to be human readable, while the **value** has some program/machine significance. As a real-world example, the PNG image format supports `5 different color-types `_, which gets encoded into a `1-byte int in the image header `_. .. code-block:: python from cyclopts import App from enum import IntEnum app = App() class ColorType(IntEnum): GRAYSCALE = 0 RGB = 2 PALETTE = 3 GRAYSCALE_ALPHA = 4 RGBA = 6 @app.default def default(color_type: ColorType = ColorType.RGB): print(f"Writing color-type value: {color_type} to the image header.") app() .. code-block:: console $ my-program Writing color-type value: 2 to the image header. $ my-program grayscale-alpha Writing color-type value: 4 to the image header. .. _Coercion Rules - Dataclasses: ******************** User-Defined Classes ******************** Cyclopts supports classically defined user classes, as well as classes defined by the following dataclass-like libraries: * `attrs `_ * `dataclass `_ * `NamedTuple `_ * `pydantic `_ * `TypedDict `_ .. note:: For ``pydantic`` classes, Cyclopts will *not* internally perform type conversions and instead relies on pydantic's coercion engine. Subkey parsing allows for assigning values positionally and by keyword with a dot-separator. .. code-block:: python from cyclopts import App from dataclasses import dataclass from typing import Literal app = App() @dataclass class User: name: str age: int region: Literal["us", "ca"] = "us" @app.default def main(user: User): print(user) app() .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ──────────────────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────────────────────────────╮ │ * USER.NAME --user.name [required] │ │ * USER.AGE --user.age [required] │ │ USER.REGION --user.region [choices: us, ca] [default: us] │ ╰─────────────────────────────────────────────────────────────────────────────────╯ $ my-program 'Bob Smith' 30 User(name='Bob Smith', age=30, region='us') $ my-program --user.name 'Bob Smith' --user.age 30 User(name='Bob Smith', age=30, region='us') $ my-program --user.name 'Bob Smith' 30 --user.region=ca User(name='Bob Smith', age=30, region='ca') Cyclopts will recursively search for :class:`~.Parameter` annotations and respect them: .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass from typing import Annotated app = App() @dataclass class User: # Beginning with "--" will completely override the parenting parameter name. name: Annotated[str, Parameter(name="--nickname")] # Not beginning with "--" will tack it on to the parenting parameter name. age: Annotated[int, Parameter(name="years-young")] @app.default def main(user: Annotated[User, Parameter(name="player")]): print(user) app() .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰───────────────────────────────────────────────────────────╯ ╭─ Parameters ──────────────────────────────────────────────╮ │ * NICKNAME --nickname [required] │ │ * PLAYER.YEARS-YOUNG [required] │ │ --player.years-young │ ╰───────────────────────────────────────────────────────────╯ ^^^^^^^^^^^^^^^^^^^^ Namespace Flattening ^^^^^^^^^^^^^^^^^^^^ The special parameter name ``"*"`` will remove the immediate parameter's name from the dotted-hierarchal name: .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass from typing import Annotated app = App() @dataclass class User: name: str age: int @app.default def main(user: Annotated[User, Parameter(name="*")]): print(user) app() .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ─────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────╮ │ * NAME --name [required] │ │ * AGE --age [required] │ ╰────────────────────────────────────────────────────────╯ This can be used to conveniently share parameters between commands, and to create a global config object. See :ref:`Sharing Parameters`. ^^^^^^^^^^ Docstrings ^^^^^^^^^^ Docstrings from the class are used for the help page. Docstrings from the command have priority over class docstrings, if supplied: .. code-block:: python from cyclopts import App from dataclasses import dataclass app = App() @dataclass class User: name: str "First and last name of the user." age: int "Age in years of the user." @app.default def main(user: User): """A short summary of what this program does. Parameters ---------- user.age: int User's age docstring from the command docstring. """ print(user) app() .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] A short summary of what this program does. ╭─ Commands ──────────────────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ────────────────────────────────────────────────────────────────────╮ │ * USER.NAME --user.name First and last name of the user. [required] │ │ * USER.AGE --user.age User's age docstring from the command docstring. │ │ [required] │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Parameter(accepts_keys=False) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the class is annotated with ``Parameter(accepts_keys=False)``, then no dot-notation subkeys are exported. The class parameter will consume enough tokens to populate the **required positional** arguments. .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass from typing import Annotated, Literal app = App() @dataclass class User: name: str age: int region: Literal["us", "ca"] = "us" @app.default def main(user: Annotated[User, Parameter(accepts_keys=False)]): print(user) app() .. code-block:: console $ my-program --help Usage: main COMMAND [ARGS] [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────────────────╮ │ * USER --user [required] │ ╰────────────────────────────────────────────────────────────────────────────────╯ $ my-program 'Bob Smith' 27 User(name='Bob Smith', age=27, region='us') $ my-program 'Bob Smith' ╭─ Error ────────────────────────────────────────────────────────────────────────╮ │ Parameter "--user" requires 2 arguments. Only got 1. │ ╰────────────────────────────────────────────────────────────────────────────────╯ In this example, we are unable to change the ``region`` parameter of ``User`` from the CLI. .. _Typer: https://typer.tiangolo.com cyclopts-3.9.0/docs/source/text_editor.rst000066400000000000000000000032741475451620500207160ustar00rootroot00000000000000=========== Text Editor =========== Some CLI programs require users to edit more complex fields in a text editor. For example, ``git`` may open a text editor for the user when rebasing or editing a commit message. While not directly related to CLI command parsing, Cyclopts provides :func:`cyclopts.edit` to satisfy this common need. Here is an example application that mimics ``git commit`` functionality. .. code-block:: python # git.py import cyclopts from textwrap import dedent import sys app = cyclopts.App(name="git") @app.command def commit(): try: response = cyclopts.edit( # blocks until text editor is closed. dedent( # removes the leading 4-tab indentation. """\ # Please enter the commit message for your changes.Lines starting # with '#' will be ignored, and an empty message aborts the commit. """ ) ) except (cyclopts.EditorDidNotSaveError, cyclopts.EditorDidNotChangeError): print("Aborting commit due to empty commit message.") sys.exit(1) filtered = "\n".join(x for x in response.split("\n") if not x.startswith("#")) filtered = filtered.strip() # remove leading/trailing whitespace. print(f"Your commit message: {filtered}") if __name__ == "__main__": app() Running ``python git.py commit`` will bring up a text editor with the pre-defined text, and then return the contents of the file. For more interactive CLI prompting, we recommend using the questionary_ package. See :func:`.edit` API page for more advanced usage. .. _questionary: https://github.com/tmbo/questionary cyclopts-3.9.0/docs/source/user_classes.rst000066400000000000000000000162361475451620500210610ustar00rootroot00000000000000============ User Classes ============ Cyclopts supports classically defined user classes, as well as classes defined by the following dataclass-like libraries: * `attrs `_ * `dataclass `_ * `NamedTuple `_ * `pydantic `_ * `TypedDict `_ As an example, lets consider using the builtin :obj:`~dataclasses.dataclass` to make a CLI that manages a movie collection. .. code-block:: python from cyclopts import App from dataclasses import dataclass app = App(name="movie-maintainer") @dataclass class Movie: title: str year: int @app.command def add(movie: Movie): print(f"Adding movie: {movie}") app() .. code-block:: console $ movie-maintainer add --help Usage: movie-maintainer add [ARGS] [OPTIONS] ╭─ Parameters ────────────────────────────────────────────────╮ │ * MOVIE.TITLE [required] │ │ --movie.title │ │ * MOVIE.YEAR --movie.year [required] │ ╰─────────────────────────────────────────────────────────────╯ $ movie-maintainer add 'Mad Max: Fury Road' 2015 Adding movie: Movie(title='Mad Max: Fury Road', year=2015) $ movie-maintainer add --movie.title 'Furiosa: A Mad Max Saga' --movie.year 2024 Adding movie: Movie(title='Furiosa: A Mad Max Saga', year=2024) In most circumstances\*, Cyclopts will also parse a json-string for a dataclass-like parameter: .. code-block:: console $ movie-maintainer add --movie='{"title": "Mad Max: Fury Road", "year": 2024}' Adding movie: Movie(title='Mad Max: Fury Road', year=2024) Json parsing will be performed when: 1. The parameter has to be specified as a keyword option; e.g. ``--movie``. 2. The referenced parameter must be dataclass-like. 3. The referenced parameter **cannot** be union'd with a ``str``. 4. The first character must be a ``{``. This behavior can be further configured via :class:`.Parameter` .. _Namespace Flattening: -------------------- Namespace Flattening -------------------- It is likely that the actual movie class/object is not important to the CLI user, and the parameter names like ``--movie.title`` are unnecessarily verbose. We can remove ``movie`` from the name by giving the ``Movie`` type annotation the special name ``"*"``. .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass from typing import Annotated app = App(name="movie-maintainer") @dataclass class Movie: title: str year: int @app.command def add(movie: Annotated[Movie, Parameter(name="*")]): print(f"Adding movie: {movie}") app() .. code-block:: console $ movie-maintainer add --help Usage: movie-maintainer add [ARGS] [OPTIONS] ╭─ Parameters ────────────────────────────────────────────────╮ │ * TITLE --title [required] │ │ * YEAR --year [required] │ ╰─────────────────────────────────────────────────────────────╯ An alternative way of supplying the :class:`.Parameter` configuration is via a decorator. This way can be cleaner and terser in many scenarios. The :class:`.Parameter` configuration will also be inherited by subclasses. .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass app = App(name="movie-maintainer") @Parameter(name="*") @dataclass class Movie: title: str year: int @app.command def add(movie: Movie): print(f"Adding movie: {movie}") app() .. _Sharing Parameters: ------------------ Sharing Parameters ------------------ A flattened dataclass provides a natural way of easily sharing a set of parameters between commands. .. code-block:: python from cyclopts import App, Parameter from dataclasses import dataclass app = App(name="movie-maintainer") @Parameter(name="*") @dataclass class Config: user: str server: str = "media.sqlite" @dataclass class Movie: title: str year: int @app.command def add(movie: Movie, *, config: Config): print(f"Config: {config}") print(f"Adding movie: {movie}") @app.command def remove(movie: Movie, *, config: Config): print(f"Config: {config}") print(f"Removing movie: {movie}") app() .. code-block:: console $ movie-maintainer remove --help Usage: movie-maintainer remove [ARGS] [OPTIONS] ╭─ Parameters ────────────────────────────────────────────────╮ │ * MOVIE.TITLE [required] │ │ --movie.title │ │ * MOVIE.YEAR --movie.year [required] │ │ * --user [required] │ │ --server [default: media.sqlite] │ ╰─────────────────────────────────────────────────────────────╯ $ movie-maintainer remove 'Mad Max: Fury Road' 2015 --user Guido Config: Config(user='Guido', server='media.sqlite') Removing movie: Movie(title='Mad Max: Fury Road', year=2015) ----------- Config File ----------- Having the user specify ``--user`` every single call is a bit cumbersome, especially if they're always going to provide the same value. We can have Cyclopts fallback to a :ref:`toml configuration file `. Consider the following toml data saved to ``config.toml``: .. code-block:: toml # config.toml user = "Guido" We can update our app to fill in missing CLI parameters from this file: .. code-block:: python from cyclopts import App, Parameter, config from dataclasses import dataclass from typing import Annotated app = App( name="movie-maintainer", config=config.Toml("config.toml", use_commands_as_keys=False), ) @Parameter(name="*") @dataclass class Config: user: str server: str = "media.sqlite" @dataclass class Movie: title: str year: int @app.command def add(movie: Movie, *, config: Config): print(f"Config: {config}") print(f"Adding movie: {movie}") app() .. code-block:: console $ movie-maintainer add 'Mad Max: Fury Road' 2015 Config: Config(user='Guido', server='media.sqlite') Adding movie: Movie(title='Mad Max: Fury Road', year=2015) cyclopts-3.9.0/docs/source/version.rst000066400000000000000000000046631475451620500200540ustar00rootroot00000000000000======= Version ======= All CLI applications should have the basic ability to check the installed version; i.e.: .. code-block:: console $ my-application --version 7.5.8 By default, Cyclopts adds a command, :meth:`--version `:, that does exactly this. Cyclopts try's to reasonably figure out your package's version by itself. The resolution order for determining the version string is as follows: 1. An explicitly supplied version string or callable to the root Cyclopts application: .. code-block:: python from cyclopts import App app = App(version="7.5.8") app() If a callable is provided, it will be invoked when running the ``--version`` command: .. code-block:: python from cyclopts import App def get_my_application_version() -> str: return "7.5.8" app = App(version=get_my_application_version) app() 2. The invoking-package's `Distribution Package's Version Number`_ via `importlib.metadata.version`_. Cyclopts attempts to derive the package module that instantiated the :class:`.App` object by traversing the call stack. 3. The invoking-package's `defacto PEP8 standard`_ ``__version__`` string. Cyclopts attempts to derive the package module that instantiated the :class:`.App` object by traversing the call stack. .. code-block:: python # mypackage/__init__.py __version__ = "7.5.8" # mypackage/__main__.py # ``App`` will use ``mypackage.__version__``. app = cyclopts.App() 4. The default version string ``"0.0.0"`` will be displayed. In short, if your CLI application is a properly structured python package, Cyclopts will automatically derive the correct version. The ``--version`` flag can be changed to a different name(s) via the ``version_flags`` parameter. .. code-block:: python app = cyclopts.App(version_flags="--show-version") app = cyclopts.App(version_flags=["--version", "-v"]) To disable the ``--version`` flag, set ``version_flags`` to an empty string or iterable. .. code-block:: python app = cyclopts.App(version_flags="") app = cyclopts.App(version_flags=[]) .. _Distribution Package's Version Number: https://packaging.python.org/en/latest/glossary/#term-Distribution-Package .. _importlib.metadata.version: https://docs.python.org/3.12/library/importlib.metadata.html#distribution-versions .. _defacto PEP8 standard: https://peps.python.org/pep-0008/#module-level-dunder-names cyclopts-3.9.0/docs/source/vs_arguably/000077500000000000000000000000001475451620500201425ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_arguably/README.rst000066400000000000000000000014201475451620500216260ustar00rootroot00000000000000=================== Arguably Comparison =================== Arguably_ is another Typer-inspired type-annotation-based CLI library. Arguably was created in response to the overly intrusive nature of Typer, with the goal of minimizing clutter and maintaining code simplicity. Like Cyclopts, Arguably mostly skirts using :obj:`Annotated` by interpreting as much data as possible from the function docstring. Unlike the :ref:`Typer comparison `, many of the topics in this section are simply comparing/contrasting with Arguably, rather than claiming to be strictly better. .. toctree:: :maxdepth: 1 :caption: Topics global_state/README.rst subcommands/README.rst .. _Arguably: https://treykeown.github.io/arguably/ .. _Typer: https://typer.tiangolo.com cyclopts-3.9.0/docs/source/vs_arguably/global_state/000077500000000000000000000000001475451620500226025ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_arguably/global_state/README.rst000066400000000000000000000035661475451620500243030ustar00rootroot00000000000000============ Global State ============ Unlike Cyclopts or Typer, with ``arguably`` you directly jump into decorating functions: .. code-block:: python import arguably @arguably.command def some_function(required, not_required=2, *others: int, option: float = 3.14): """ this function is on the command line! Args: required: a required argument not_required: this one isn't required, since it has a default value *others: all the other positional arguments go here option: [-x] keyword-only args are options, short name is in brackets """ print(f"{required=}, {not_required=}, {others=}, {option=}") if __name__ == "__main__": arguably.run() With Arguably, no application object is created. This immediately becomes an issue if you use a library that uses arguably on import. Lets consider the following file: .. code-block:: python # library_using_arguably.py import arguably @arguably.command def some_library_function(name): print(f"{name=}") if __name__ == "__main__": arguably.run() .. code-block:: console $ python library_using_arguably.py foo name='foo' So this by itself works fine, but lets create another script that imports this library: .. code-block:: python import arguably import library_using_arguably @arguably.command def my_function(name): print(f"{name=}") if __name__ == "__main__": arguably.run() Now, lets check the help screen: .. code-block:: console $ python my-script.py --help usage: my-script.py [-h] command ... positional arguments: command some-library-function my-function options: -h, --help show this help message and exit The two CLI applications got combined into one, making Arguably dangerous for CLIs that are also libraries. cyclopts-3.9.0/docs/source/vs_arguably/global_state/library_using_arguably.py000066400000000000000000000002131475451620500277070ustar00rootroot00000000000000import arguably @arguably.command def some_library_function(name): print(f"{name=}") if __name__ == "__main__": arguably.run() cyclopts-3.9.0/docs/source/vs_arguably/global_state/my-script.py000066400000000000000000000002551475451620500251050ustar00rootroot00000000000000import arguably import library_using_arguably # noqa: F401 @arguably.command def my_function(name): print(f"{name=}") if __name__ == "__main__": arguably.run() cyclopts-3.9.0/docs/source/vs_arguably/subcommands/000077500000000000000000000000001475451620500224555ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_arguably/subcommands/README.rst000066400000000000000000000041721475451620500241500ustar00rootroot00000000000000=========== Subcommands =========== Arguably parses the command tree based on ``__`` delimited function names. .. code-block:: python import arguably @arguably.command def ec2__start_instances(*instances): """Start instances. Args: *instances: {instance}s to start """ for inst in instances: print(f"Starting {inst}") @arguably.command def ec2__stop_instances(*instances): """Stop instances. Args: *instances: {instance}s to stop """ for inst in instances: print(f"Stopping {inst}") if __name__ == "__main__": arguably.run() .. code-block:: console $ python main.py ec2 --help positional arguments: command start-instances start instances. stop-instances stop instances. Cyclopts handles the command tree by creating and registering recursive :class:`App ` objects: .. code-block:: python from cyclopts import App app = App() app.command(ec2 := App(name="ec2")) @ec2.command def start_instances(*instances): """Start instances. Args: *instances: {instance}s to start """ for inst in instances: print(f"Starting {inst}") @ec2.command def stop_instances(*instances): """Stop instances. Args: *instances: {instance}s to stop """ for inst in instances: print(f"Stopping {inst}") if __name__ == "__main__": app() .. code-block:: console $ python main.py ec2 --help ╭─ Commands ───────────────────────────────────────────────────────────╮ │ start-instances start instances. │ │ stop-instances stop instances. │ ╰──────────────────────────────────────────────────────────────────────╯ cyclopts-3.9.0/docs/source/vs_arguably/subcommands/main.py000066400000000000000000000017041475451620500237550ustar00rootroot00000000000000import arguably @arguably.command def ec2__start_instances(*instances): """Start instances. Args: *instances: {instance}s to start """ for inst in instances: print(f"Starting {inst}") @arguably.command def ec2__stop_instances(*instances): """Stop instances. Args: *instances: {instance}s to stop """ for inst in instances: print(f"Stopping {inst}") if __name__ == "__main__": arguably.run() from cyclopts import App app = App() app.command(ec2 := App(name="ec2")) @ec2.command def start_instances(*instances): """Start instances. Args: *instances: {instance}s to start """ for inst in instances: print(f"Starting {inst}") @ec2.command def stop_instances(*instances): """Stop instances. Args: *instances: {instance}s to stop """ for inst in instances: print(f"Stopping {inst}") if __name__ == "__main__": app() cyclopts-3.9.0/docs/source/vs_fire/000077500000000000000000000000001475451620500172615ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_fire/README.rst000066400000000000000000000037321475451620500207550ustar00rootroot00000000000000=============== Fire Comparison =============== Fire_ is a CLI parsing library by Google that attempts to generate a CLI from any Python object. To that end, I think Fire definitely achieves its goal. However, I think Fire has too much magic, and not enough control. From the `Fire documentation`_: The types of the arguments are determined by their values, rather than by the function signature where they're used. You can pass any Python literal from the command line: numbers, strings, tuples, lists, dictionaries, (sets are only supported in some versions of Python). You can also nest the collections arbitrarily as long as they only contain literals. Essentially, Fire ignores type hints and parses CLI parameters as if they were python expressions. .. code-block:: python import fire def hello(name: str = "World"): print(f"{name=} {type(name)=}") if __name__ == "__main__": fire.Fire(hello) .. code-block:: console $ my-script foo name='foo' type(name)= $ my-script 100 name=100 type(name)= $ my-script true name='true' type(name)= $ my-script True name=True type(name)= The equivalent in Cyclopts: .. code-block:: python import cyclopts app = cyclopts.App() @app.default def hello(name: str = "World"): print(f"{name=} {type(name)=}") if __name__ == "__main__": app() .. code-block:: console $ my-script foo name='foo' type(name)= $ my-script 100 name='100' type(name)= $ my-script true name='true' type(name)= $ my-script True name='True' type(name)= Fire is fine for some quick prototyping, but is not suitable for a serious CLI. Therefore, I wouldn't say Fire is a direct competitor to Cyclopts. .. _Fire: https://github.com/google/python-fire .. _Fire documentation: https://github.com/google/python-fire/blob/master/docs/guide.md#argument-parsing cyclopts-3.9.0/docs/source/vs_typer/000077500000000000000000000000001475451620500174775ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/README.rst000066400000000000000000000032411475451620500211660ustar00rootroot00000000000000.. _Typer Comparison: ================ Typer Comparison ================ Much of Cyclopts was inspired by the excellent `Typer`_ library. Despite its popularity, Typer has some traits that I (and others) find less than ideal. Part of this stems from Typer's age, with its first release in late 2019, soon after Python 3.8's release. Because of this, most of its API was initially designed around assigning proxy default values to function parameters. This made the decorated command functions difficult to use outside of Typer. With the introduction of :obj:`~.typing.Annotated` in python3.9, type-hints were able to be directly annotated, allowing for the removal of these proxy defaults. Additionally, Typer is built on top of `Click`_. This makes it difficult for newcomers to figure out which elements are Typer-related and which elements are click-related. It's also hard to tell whether the following criticisms stem from Typer, or the underlying Click. For better-or-worse, Cyclopts uses its own internal parsing strategy, gaining complete control over the process. This section was written about the current version of Typer: ``v0.9.0``. .. toctree:: :maxdepth: 1 :caption: Topics argument_vs_option/README.rst positional_or_keyword/README.rst choices/README.rst default_command/README.rst docstring/README.rst decorator_parentheses/README.rst optional_list/README.rst keyword_multiple_values/README.rst flag_negation/README.rst help_defaults/README.rst validation/README.rst union_support/README.rst version_flag/README.rst documentation/README.rst .. _Typer: https://typer.tiangolo.com .. _Click: https://click.palletsprojects.com cyclopts-3.9.0/docs/source/vs_typer/argument_vs_option/000077500000000000000000000000001475451620500234215ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/argument_vs_option/README.rst000066400000000000000000000036011475451620500251100ustar00rootroot00000000000000.. _Typer Argument vs Option: ================== Argument vs Option ================== In Typer, the actual difference between the ``Argument`` and ``Option`` classes aren't very clear. Generally, it can be said that: * Arguments are **required** and provided as positional arguments. * Options are **optional** and are provided as keyword arguments, generally preceded with a ``--``. With more modern python type annotations, this distinction is unnecessary, because parameters (positional or keyword) can be determined directly from the function signature. Consider the following function signatures: .. code-block:: python def pos_or_keyword(a, b): pass def pos_only(a, b, /): pass def keyword_only(*, a, b=2): pass def mixture(a, /, b, *, c=3): pass If you aren't familiar with these declarations, refer to the official PEP570_, or `a more user-friendly tutorial`_. From these function signatures, we can deduce: 1. Which parameters are position-only, keyword-only, or both. 2. Which parameters are required, by their lack of defaults. Because of these builtin python mechanisms, Cyclopts has a single :class:`Parameter ` class used for providing additional parameter metadata. I believe that Typer's separate ``Argument`` and ``Option`` classes are a relic from when they must be supplied as a parameter's proxy default value. .. code-block:: python app = typer.Typer() @app.command() def foo(a=Argument(), b=Option(default=2)): pass When used as such, we lose the ability to define the function signature with position-only or keyword-only markers. We also lose the ability to directly inspect which parameters are optional by having "real" defaults and which ones are required. .. _PEP570: https://peps.python.org/pep-0570/ .. _a more user-friendly tutorial: https://realpython.com/lessons/positional-only-arguments/ cyclopts-3.9.0/docs/source/vs_typer/argument_vs_option/main.py000066400000000000000000000000001475451620500247050ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/choices/000077500000000000000000000000001475451620500211145ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/choices/README.rst000066400000000000000000000043641475451620500226120ustar00rootroot00000000000000.. _Typer Choices: ======= Choices ======= ---- Enum ---- Frequently, a CLI will want to limit values provided to a parameter to a specific set of choices. With Typer, this is accomplished via declaring an :class:`~enum.Enum`. .. code-block:: python import typer from enum import Enum class Environment(str, Enum): # Values end in "_value" to avoid confusion in this example. DEV = "dev_value" STAGING = "staging_value" PROD = "prod_value" typer_app = typer.Typer() @typer_app.command def foo(env: Environment = Environment.DEV): print(f"Using: {env.name}") print("Typer (Enum):") typer_app(["--env", "staging_value"]) # Using: STAGING Typer looks for the CLI-provided *value*, and supplies the function with the enum member. IMHO, this is backwards; typically the enum name (e.g. ``DEV``) is intended to be more human-friendly, while the value (e.g. ``dev_value``) more frequently has a programmatic-meaning. **When using enums, Cyclopts will do the opposite of Typer**, performing a **case-insensitive** lookup by **name**. .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(env: Environment = Environment.DEV): print(f"Using: {env.name}") print("Cyclopts (Enum):") cyclopts_app(["--env", "staging"]) # Using: STAGING ------- Literal ------- Enums don't work well with everyone's workflow. Many people prefer to directly use strings for their functions' options. The much more intuitive, convenient method of doing this is with the :obj:`~typing.Literal` type annotation. Unfortuneately, Typer has not provided support, despite `a feature request dating back to early 2020`_ Cyclopts has builtin support for :obj:`~typing.Literal`, see :ref:`Coercion Rules - Literal `. .. code-block:: python import cyclopts from typing import Literal cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(env: Literal["dev", "staging", "prod"] = "staging"): print(f"Using: {env}") print("Cyclopts (Literal):") cmd = ["--env", "staging"] print(cmd) cyclopts_app(cmd) # Using: staging .. _a feature request dating back to early 2020: https://github.com/tiangolo/typer/issues/76 cyclopts-3.9.0/docs/source/vs_typer/choices/main.py000066400000000000000000000016721475451620500224200ustar00rootroot00000000000000from enum import Enum from typing import Literal import typer import cyclopts class Environment(str, Enum): DEV = "dev_value" STAGING = "staging_value" PROD = "prod_value" typer_app = typer.Typer() @typer_app.command() def foo(env: Environment = Environment.DEV): env = env.name print(f"Using: {env}") print("Typer (Enum):") cmd = ["--env", "staging_value"] print(cmd) typer_app(cmd, standalone_mode=False) # Using: STAGING cyclopts_app = cyclopts.App() @cyclopts_app.default() def foo(env: Environment = Environment.DEV): env = env.name print(f"Using: {env}") print("Cyclopts (Enum):") cmd = ["--env", "staging"] print(cmd) cyclopts_app(cmd) # Using: STAGING cyclopts_app = cyclopts.App() @cyclopts_app.default() def foo(env: Literal["dev", "staging", "prod"] = "staging"): print(f"Using: {env}") print("Cyclopts (Literal):") cmd = ["--env", "staging"] print(cmd) cyclopts_app(cmd) # Using: staging cyclopts-3.9.0/docs/source/vs_typer/decorator_parentheses/000077500000000000000000000000001475451620500240625ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/decorator_parentheses/README.rst000066400000000000000000000010731475451620500255520ustar00rootroot00000000000000===================== Decorator Parentheses ===================== A minor nitpick, but all of Typer's decorators require parentheses. .. code-block:: python import typer typer_app = typer.Typer() # This doesn't work! Missing () @typer_app.command def foo(): pass Cyclopts works with and without parentheses. .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() # This works! Missing () @cyclopts_app.command def foo(): pass # This also works. @cyclopts_app.command() def bar(): pass cyclopts-3.9.0/docs/source/vs_typer/default_command/000077500000000000000000000000001475451620500226215ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/default_command/README.rst000066400000000000000000000036241475451620500243150ustar00rootroot00000000000000.. _Typer Default Command: =============== Default Command =============== Typer has an annoying design quirk where if you register a single command, it **won't** expect you to provide the command name in the CLI. For example: .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def foo(): print("FOO") typer_app([], standalone_mode=False) # FOO typer_app(["foo"], standalone_mode=False) # raises exception: Got unexpected extra argument (foo) Once you add a second command, then the CLI expects the command to be provided: .. code-block:: python typer_app(["foo"], standalone_mode=False) # FOO typer_app(["bar"], standalone_mode=False) # BAR `This behavior catches many people off guard.`_ If you want a single command, you have to unintuitively declare a ``callback``. Github user `ajlive's callback solution`_ is copied below. .. code-block:: python @app.callback() def dummy_to_force_subcommand() -> None: """ This function exists because Typer won't let you force a single subcommand. Since we know we will add other subcommands in the future and don't want to break the interface, we have to use this workaround. Delete this when a second subcommand is added. """ pass To avoid this confusion, Cyclopts has two ways of registering a function: 1. :meth:`@app.command <.App.command>` - Register a function as a command. 2. :meth:`@app.default <.App.default>` - Invoked if no registered command can be parsed from the CLI. .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() @cyclopts_app.command def foo(): print("FOO") cyclopts_app(["foo"]) # FOO .. _This behavior catches many people off guard.: https://github.com/tiangolo/typer/issues/315 .. _ajlive's callback solution: https://github.com/tiangolo/typer/issues/315#issuecomment-1142593959 cyclopts-3.9.0/docs/source/vs_typer/default_command/main.py000066400000000000000000000012121475451620500241130ustar00rootroot00000000000000import typer import cyclopts typer_app = typer.Typer() @typer_app.command() def foo(): print("FOO") typer_app([], standalone_mode=False) # FOO try: typer_app(["foo"], standalone_mode=False) except Exception as e: print(f"EXCEPTION: {e}") # EXCEPTION: Got unexpected extra argument (foo) @typer_app.command() def bar(): print("BAR") typer_app(["foo"], standalone_mode=False) # FOO typer_app(["bar"], standalone_mode=False) # BAR cyclopts_app = cyclopts.App() @cyclopts_app.command def foo(): print("FOO") cyclopts_app(["foo"]) # FOO @cyclopts_app.command def bar(): print("BAR") cyclopts_app(["bar"]) # BAR cyclopts-3.9.0/docs/source/vs_typer/docstring/000077500000000000000000000000001475451620500214735ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/docstring/README.rst000066400000000000000000000104701475451620500231640ustar00rootroot00000000000000.. _Typer Docstring Parsing: ================= Docstring Parsing ================= Typer performs no docstring parsing. Frequently, Typer's Argument/Option is only used to provide a ``help`` string. However, this ``help`` string commonly mirrors the function's docstring. Consider the following Typer program: .. code-block:: python import typer typer_app = typer.Typer() @typer_app.callback() def dummy(): # So that ``foo`` is considered a command. pass @typer_app.command() def foo(bar): """Foo Docstring. Parameters ---------- bar: str Bar parameter docstring. """ typer_app() .. code-block:: console $ my-script --help ╭─ Commands ────────────────────────────────────────────────────────────╮ │ foo Foo Docstring. │ ╰───────────────────────────────────────────────────────────────────────╯ $ my-script foo --help Foo Docstring. Parameters ---------- bar: str Bar parameter docstring. ╭─ Arguments ───────────────────────────────────────────────────────────╮ │ * bar TEXT [default: None] [required] │ ╰───────────────────────────────────────────────────────────────────────╯ The ``foo`` command's short description was properly parsed from the docstring. However, it mangles the Numpy-style docstring (or any docstring format for that matter) and doesn't correctly display ``bar``'s help. Typer just displays the entire docstring. To achieve the desired result with Typer, we have to explicitly annotate the parameter ``bar``: .. code-block:: python @typer_app.command() def foo(bar: Annotated[str, Argument(help="Bar parameter docstring.")]): ... For any serious application, this means that every function parameter must be annotated this way, significantly bloating the function signature. Compare this to Cyclopts: .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() @cyclopts_app.command() def foo(bar): """Foo Docstring. Parameters ---------- bar: str Bar parameter docstring. """ cyclopts_app() .. code-block:: console $ my-script --help ╭─ Commands ────────────────────────────────────────────────────────────╮ │ foo Foo Docstring. │ ╰───────────────────────────────────────────────────────────────────────╯ $ my-script foo --help Foo Docstring. ╭─ Parameters ──────────────────────────────────────────────────────────╮ │ * BAR,--bar Bar parameter docstring. [required] │ ╰───────────────────────────────────────────────────────────────────────╯ Cyclopts did not mangle the docstring into the long description, and it correctly parsed ``bar``'s help. This ends up significantly simplifying function signatures in the common situation where just a help string needs to be added. The common case in Cyclopts does not require the lengthy ``Annotated[str, Parameter(help="Bar parameter docstring")]``. Internally, Cyclopts uses the excellent `docstring_parser`_ library for parsing docstrings. Check their project out! .. _docstring_parser: https://github.com/rr-/docstring_parser cyclopts-3.9.0/docs/source/vs_typer/docstring/main.py000066400000000000000000000053251475451620500227760ustar00rootroot00000000000000import typer import cyclopts typer_app = typer.Typer() @typer_app.callback() def dummy(): # So that ``foo`` is considered a command. pass @typer_app.command() def foo(bar): """Foo Docstring. Parameters ---------- bar: str Bar parameter docstring. """ pass print("Typer:") # Typer correctly parses the docstring short description. typer_app(["--help"], standalone_mode=False) # ╭─ Commands ─────────────────────────────────────────────────────╮ # │ foo Foo Docstring. │ # ╰────────────────────────────────────────────────────────────────╯ # However, it fails at parsing the rest of the docstring. typer_app(["foo", "--help"], standalone_mode=False) # Foo Docstring. # Parameters ---------- bar: str Bar parameter docstring. # # ╭─ Arguments ────────────────────────────────────────────────────╮ # │ * bar TEXT [default: None] [required] │ # ╰────────────────────────────────────────────────────────────────╯ cyclopts_app = cyclopts.App() @cyclopts_app.command() def foo(bar): """Foo Docstring. Parameters ---------- bar: str Bar parameter docstring. """ pass print("Cyclopts:") # Cyclopts also properly parses the short description. cyclopts_app(["--help"]) # ╭─ Commands ─────────────────────────────────────────────────────╮ # │ foo Foo Docstring. │ # ╰────────────────────────────────────────────────────────────────╯ cyclopts_app(["foo", "--help"]) # Foo Docstring. # # ╭─ Parameters ───────────────────────────────────────────────────╮ # │ * BAR,--bar Bar parameter docstring. [required] │ # ╰────────────────────────────────────────────────────────────────╯ cyclopts-3.9.0/docs/source/vs_typer/documentation/000077500000000000000000000000001475451620500223505ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/documentation/README.rst000066400000000000000000000010531475451620500240360ustar00rootroot00000000000000============= Documentation ============= Documentation is a major component of any library. Typer's documentation contains many good tutorials and demonstrations on how to use the library, **but has very little information on the API itself**. Frequently the only way to discover options and behavior is to dive into the source code. This becomes further confusing as the lines of where Typer ends and Click begins is quite blurred. Cyclopts has a full :ref:`API` page, containing all the configurable options and defined behaviors in a single place. cyclopts-3.9.0/docs/source/vs_typer/flag_negation/000077500000000000000000000000001475451620500222745ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/flag_negation/README.rst000066400000000000000000000065611475451620500237730ustar00rootroot00000000000000.. _Typer Flag Negation: ============= Flag Negation ============= For boolean parameters, Typer adds a ``--no-MY-FLAG-NAME`` to specify a ``False`` argument. .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def foo(my_flag: bool = False): print(f"{my_flag=}") typer_app(["--my-flag"], standalone_mode=False) # my_flag=True typer_app(["--no-my-flag"], standalone_mode=False) # my_flag=False Overriding the option's name will disable Typer's negative-flag generation logic: .. code-block:: python import typer from typing import Annotated typer_app = typer.Typer() @typer_app.command() def foo(my_flag: Annotated[bool, Option("--my-flag")] = False): print(f"{my_flag=}") typer_app(["--my-flag"], standalone_mode=False) # my_flag=True typer_app(["--no-my-flag"], standalone_mode=False) # NoSuchOption: No such option: --no-my-flag This is not the worst, but there is a tiny bit of duplication. To use a different negative flag, you can supply the name after a slash in your option-name-string. .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def foo(my_flag: Annotated[bool, Option("--my-flag/--your-flag")] = False): print(f"{my_flag=}") typer_app(["--my-flag"], standalone_mode=False) # my_flag=True typer_app(["--your-flag"], standalone_mode=False) # my_flag=False Cyclopts's :class:`~.Parameter` takes in an optional :attr:`~.Parameter.negative` flag. To suppress the negative-flag generation, set this argument to either an empty string or list. .. code-block:: python import cyclopts from typing import Annotated cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(my_flag: Annotated[bool, cyclopts.Parameter(negative="")] = False): print(f"{my_flag=}") print("Cyclopts:") cyclopts_app(["--my-flag"]) # my_flag=True cyclopts_app(["--your-flag"], exit_on_error=False) # ╭─ Error ─────────────────────────────────────────────────────────────────────╮ # │ Error converting value "--your-flag" to for "--my-flag". │ # ╰─────────────────────────────────────────────────────────────────────────────╯ # CoercionError: Error converting value "--your-flag" to for "--my-flag". To define your own custom negative flag, just provide it as a string or list of strings. .. code-block:: python @cyclopts_app.default def foo(my_flag: Annotated[bool, cyclopts.Parameter(negative="--your-flag")] = False): print(f"{my_flag=}") print("Cyclopts:") cyclopts_app(["--my-flag"]) # my_flag=True cyclopts_app(["--your-flag"]) # my_flag=False The default ``--no-`` negation prefix can also be customized with :attr:`~.Parameter.negative_bool`. .. code-block:: python @cyclopts_app.default def foo(my_flag: Annotated[bool, cyclopts.Parameter(negative_bool="--disable-")] = False): print(f"{my_flag=}") print("Cyclopts:") cyclopts_app(["--my-flag"]) # my_flag=True cyclopts_app(["--disable-my-flag"]) # my_flag=False cyclopts-3.9.0/docs/source/vs_typer/flag_negation/main.py000066400000000000000000000014611475451620500235740ustar00rootroot00000000000000from typing import Annotated import typer import cyclopts typer_app = typer.Typer() @typer_app.command() def foo(my_flag: bool = False): print(f"{my_flag=}") print("Typer:") typer_app(["--my-flag"], standalone_mode=False) typer_app(["--no-my-flag"], standalone_mode=False) # my_flag=True # my_flag=False cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(my_flag: bool = False): print(f"{my_flag=}") print("Cyclopts:") cyclopts_app(["--my-flag"]) cyclopts_app(["--no-my-flag"]) # my_flag=True # my_flag=False cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(my_flag: Annotated[bool, cyclopts.Parameter(negative="--your-flag")] = False): print(f"{my_flag=}") print("Cyclopts:") cyclopts_app(["--my-flag"]) cyclopts_app(["--your-flag"]) # my_flag=True # my_flag=False cyclopts-3.9.0/docs/source/vs_typer/help_defaults/000077500000000000000000000000001475451620500223165ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/help_defaults/README.rst000066400000000000000000000071131475451620500240070ustar00rootroot00000000000000============= Help Defaults ============= In Typer's ``--help`` display, default values are unhelpfully shown for required arguments. .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def compress( src: Annotated[Path, typer.Argument(help="File to compress.")], dst: Annotated[Path, typer.Argument(help="Path to save compressed data to.")] = Path("out.zip"), ): print(f"Compressing data from {src} to {dst}") print("Typer positional:") typer_app(["--help"], standalone_mode=False) # ╭─ Arguments ───────────────────────────────────────────────────────────────╮ # │ * src PATH File to compress. [default: None] [required] │ # │ dst [DST] Path to save compressed data to. [default: out.zip] │ # ╰───────────────────────────────────────────────────────────────────────────╯ It doesn't make any sense to show a default for a parameter that is required and has no default. Cyclopts fixes this: .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() @cyclopts_app.default() def compress( src: Annotated[Path, cyclopts.Parameter(help="File to compress.")], dst: Annotated[Path, cyclopts.Parameter(help="Path to save compressed data to.")] = Path("out.zip"), ): print(f"Compressing data from {src} to {dst}") cyclopts_app(["--help"]) # ╭─ Parameters ───────────────────────────────────────────────────────╮ # │ * SRC,--src File to compress. [required] │ # │ DST,--dst Path to save compressed data to. [default: out.zip] │ # ╰────────────────────────────────────────────────────────────────────╯ Additionally, if the default value is :obj:`None`, cyclopts's default configuration will **not** display ``[default: None]``. Doing so doesn't convey much meaning to the end-user. Typically :obj:`None` is a sentinel value who's true value gets set inside the function. Additionally, the cleaner, docstring-centric way of writing this program with Cyclopts would be: .. code-block:: python import cyclopts from pathlib import Path cyclopts_app = cyclopts.App() @cyclopts_app.default() def compress(src: Path, dst: Path = Path("out.zip")): """Compress a file. Parameters ---------- src: Path File to compress. dst: Path Path to save compressed data to. """ print(f"Compressing data from {src} to {dst}") cyclopts_app(["--help"]) # ╭─ Parameters ───────────────────────────────────────────────────────╮ # │ * SRC,--src File to compress. [required] │ # │ DST,--dst Path to save compressed data to. [default: out.zip] │ # ╰────────────────────────────────────────────────────────────────────╯ cyclopts-3.9.0/docs/source/vs_typer/help_defaults/main.py000066400000000000000000000036311475451620500236170ustar00rootroot00000000000000from pathlib import Path from typing import Annotated import typer import cyclopts typer_app = typer.Typer() @typer_app.command() def compress( src: Annotated[Path, typer.Argument(help="File to compress.")], dst: Annotated[Path, typer.Argument(help="Path to save compressed data to.")] = Path("out.zip"), ): print(f"Compressing data from {src} to {dst}") print("Typer positional:") typer_app(["--help"], standalone_mode=False) # ╭─ Arguments ───────────────────────────────────────────────────────────────╮ # │ * src PATH File to compress. [default: None] [required] │ # │ dst [DST] Path to save compressed data to. [default: out.zip] │ # ╰───────────────────────────────────────────────────────────────────────────╯ cyclopts_app = cyclopts.App() @cyclopts_app.default() def compress( src: Annotated[Path, cyclopts.Parameter(help="File to compress.")], dst: Annotated[Path, cyclopts.Parameter(help="Path to save compressed data to.")] = Path("out.zip"), ): print(f"Compressing data from {src} to {dst}") cyclopts_app(["--help"]) # ╭─ Parameters ───────────────────────────────────────────────────────╮ # │ * SRC,--src File to compress. [required] │ # │ DST,--dst Path to save compressed data to. [default: out.zip] │ # ╰────────────────────────────────────────────────────────────────────╯ cyclopts-3.9.0/docs/source/vs_typer/keyword_multiple_values/000077500000000000000000000000001475451620500244555ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/keyword_multiple_values/README.rst000066400000000000000000000040541475451620500261470ustar00rootroot00000000000000======================= Keyword Multiple Values ======================= In some applications, it is desirable to supply multiple values to a keyword argument. For example, lets consider an application where we want to specify multiple input files. We want our application to look like the following: .. code-block:: console $ my-program output.bin --input input1.bin input2.bin input3.bin In Typer, `it is impossible to accomplish this `_. With Typer, the keyword must be specified before each value: .. code-block:: console $ my-program output.bin --input input1.bin --input input2.bin --input input3.bin By default, Cyclopts behavior mimics Typer, where a single element worth of CLI tokens are consumed. However, by setting :attr:`.Parameter.consume_multiple` to :obj:`True`, multiple elements worth of CLI tokens will be consumed. Consider the following example program with a single output path, and multiple input paths. .. code-block:: python from cyclopts import App, Parameter from pathlib import Path from typing import Annotated app = App() @app.default def main(output: Path, input: Annotated[list[Path], Parameter(consume_multiple=True)]): print(f"{input=} {output=}") if __name__ == "__main__": app() All of the following invocations are equivalent: .. code-block:: console $ my-program out.bin input1.bin input2.bin input3.bin input=[PosixPath('input1.bin'), PosixPath('input2.bin'), PosixPath('input3.bin')] output=PosixPath('out.bin') $ my-program out.bin --input input1.bin --input input2.bin --input input3.bin $ my-program out.bin --input input1.bin input2.bin input3.bin $ my-program --input input1.bin input2.bin input3.bin --output out.bin $ my-program --input input1.bin input2.bin input3.bin -- output.bin To set this configuration for your entire application, supply it to your root :attr:`.App.default_parameter`: .. code-block:: python from cyclopts import App, Parameter app = App(default_parameter=Parameter(consume_multiple=True)) cyclopts-3.9.0/docs/source/vs_typer/optional_list/000077500000000000000000000000001475451620500223575ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/optional_list/README.rst000066400000000000000000000047401475451620500240530ustar00rootroot00000000000000.. _Typer Optional Lists: ============== Optional Lists ============== .. note:: This issue has been addressed in `Typer v0.10.0`_. Typer does not handle optional lists particularly well. In Typer, if a list argument is not provided via the CLI, an empty list is passed to the command by default. While this might be acceptable in some scenarios, it can be unexpected and differs semantically from the default value. Because lists are mutable, and `setting mutable defaults is strongly discouraged`_, setting list parameters' default to :obj:`None` is common practice. This approach can also help differentiate between the intention of using a default list and explicitly requesting an empty list. Consider the following Typer example: .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def foo(favorite_numbers: Optional[list[int]] = None): if favorite_numbers is None: favorite_numbers = [1, 2, 3] print(f"My favorite numbers are: {favorite_numbers}") typer_app(["--favorite-numbers", "100", "--favorite-numbers", "200"], standalone_mode=False) # My favorite numbers are: [100, 200] typer_app([], standalone_mode=False) # My favorite numbers are: [] In this example, we expect the default list ``[1, 2, 3]`` to be used when no input is provided. However, Typer supplies an empty list instead of :obj:`None`. Cyclopts has a more intuitive solution. If no CLI option is specified, no argument is bound, so the parameter's default value :obj:`None` is used. If we wish to pass an empty iterable (e.g. :class:`set` or :class:`list`), Cyclopts provides an ``--empty-*`` flag for each iterable parameter. This feature is configurable via :attr:`.Parameter.negative_iterable`. .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() @cyclopts_app.default() def foo(favorite_numbers: Optional[list[int]] = None): if favorite_numbers is None: favorite_numbers = [1, 2, 3] print(f"My favorite numbers are: {favorite_numbers}") cyclopts_app(["--favorite-numbers", "100", "--favorite-numbers", "200"]) # My favorite numbers are: [100, 200] cyclopts_app([]) # My favorite numbers are: [1, 2, 3] cyclopts_app(["--empty-favorite-numbers"]) # My favorite numbers are: [] .. _setting mutable defaults is strongly discouraged: https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments .. _Typer v0.10.0: https://github.com/tiangolo/typer/releases/tag/0.10.0 cyclopts-3.9.0/docs/source/vs_typer/optional_list/main.py000066400000000000000000000022111475451620500236510ustar00rootroot00000000000000from typing import Optional import typer import cyclopts typer_app = typer.Typer() @typer_app.command() def foo(favorite_numbers: Optional[list[int]] = None): if favorite_numbers is None: favorite_numbers = [1, 2, 3] print(f"My favorite numbers are: {favorite_numbers}") print("Typer with arguments:") typer_app(["--favorite-numbers", "100", "--favorite-numbers", "200"], standalone_mode=False) # My favorite numbers are: [100, 200] print("Typer without arguments:") typer_app([], standalone_mode=False) # My favorite numbers are: [] cyclopts_app = cyclopts.App() @cyclopts_app.default() def foo(favorite_numbers: Optional[list[int]] = None): if favorite_numbers is None: favorite_numbers = [1, 2, 3] print(f"My favorite numbers are: {favorite_numbers}") print("Cyclopts with arguments:") cyclopts_app(["--favorite-numbers", "100", "--favorite-numbers", "200"]) # My favorite numbers are: [100, 200] print("Cyclopts without arguments:") cyclopts_app([]) # My favorite numbers are: [1, 2, 3] print("Cyclopts with --empty-favorite-numbers:") cyclopts_app(["--empty-favorite-numbers"]) # My favorite numbers are: [] cyclopts-3.9.0/docs/source/vs_typer/positional_or_keyword/000077500000000000000000000000001475451620500241245ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/positional_or_keyword/README.rst000066400000000000000000000021011475451620500256050ustar00rootroot00000000000000=============================== Positional or Keyword Arguments =============================== A limitation of Typer is that a parameter cannot be both positional and keyword. For example, lets say we want to implement a ``mv``\-like program that takes in a source path, and a destination path: .. code-block:: python typer_app = typer.Typer() @typer_app.command() def mv(src, dst): print(f"Moving {src} -> {dst}") typer_app(["foo", "bar"], standalone_mode=False) # Moving foo -> bar The code works when supplying the inputs as positional arguments, but fails when trying to specify them as keywords. .. code-block:: python print("Typer keyword:") typer_app(["--src", "foo", "--dst", "bar"], standalone_mode=False) # No such option: --src Cyclopts handles both situations: .. code-block:: python cyclopts_app = cyclopts.App() @cyclopts_app.default() def mv(src, dst): print(f"Moving {src} -> {dst}") cyclopts_app(["foo", "bar"]) # Moving foo -> bar cyclopts_app(["--src", "foo", "--dst", "bar"]) # Moving foo -> bar cyclopts-3.9.0/docs/source/vs_typer/positional_or_keyword/main.py000066400000000000000000000013151475451620500254220ustar00rootroot00000000000000import typer import cyclopts typer_app = typer.Typer() @typer_app.command() def mv(src, dst): print(f"Moving {src} -> {dst}") print("Typer positional:") typer_app(["foo", "bar"], standalone_mode=False) # Moving foo -> bar print("Typer keyword:") try: typer_app(["--src", "foo", "--dst", "bar"], standalone_mode=False) except Exception as e: print("EXCEPTION: " + str(e)) # EXCEPTION: No such option: --src cyclopts_app = cyclopts.App() @cyclopts_app.default() def mv(src, dst): print(f"Moving {src} -> {dst}") print("Cyclopts positional:") cyclopts_app(["foo", "bar"]) # Moving foo -> bar print("Cyclopts keyword:") cyclopts_app(["--src", "foo", "--dst", "bar"]) # Moving foo -> bar cyclopts-3.9.0/docs/source/vs_typer/union_support/000077500000000000000000000000001475451620500224235ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/union_support/README.rst000066400000000000000000000021461475451620500241150ustar00rootroot00000000000000====================== Union/Optional Support ====================== Currently, Typer does not support :obj:`~typing.Union` type annotations. .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def foo(value: Union[int, str] = "default_str"): print(f"{type(value)=} {value=}") typer_app(["123"]) # AssertionError: Typer Currently doesn't support Union types Cyclopts fully supports :obj:`~typing.Union` annotations. Cyclopt's :ref:`Coercion Rules ` iterate left-to-right over the unioned types until a coercion can be performed without error. .. code-block:: python import cyclopts cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(value: Union[int, str] = "default_str"): print(f"{type(value)=} {value=}") print("Cyclopts:") cyclopts_app(["123"]) # type(value)= value=123 cyclopts_app(["bar"]) # type(value)= value='bar' Naturally, Cyclopts also supports :obj:`~typing.Optional` types, since :obj:`~typing.Optional` is syntactic sugar for ``Union[..., None]``. cyclopts-3.9.0/docs/source/vs_typer/union_support/main.py000066400000000000000000000012051475451620500237170ustar00rootroot00000000000000from typing import Union import typer import cyclopts typer_app = typer.Typer() @typer_app.command() def foo(value: Union[int, str] = "default_str"): print(f"{type(value)=} {value=}") print("Typer:") try: typer_app(["123"], standalone_mode=False) except Exception as e: print(e) # AssertionError: Typer Currently doesn't support Union types cyclopts_app = cyclopts.App() @cyclopts_app.default def foo(value: Union[int, str] = "default_str"): print(f"{type(value)=} {value=}") print("Cyclopts:") cyclopts_app(["123"]) # type(value)= value=123 cyclopts_app(["bar"]) # type(value)= value='bar' cyclopts-3.9.0/docs/source/vs_typer/validation/000077500000000000000000000000001475451620500216315ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/validation/README.rst000066400000000000000000000061751475451620500233310ustar00rootroot00000000000000========== Validation ========== Typer has builtin argument validation for certain type annotations. .. code-block:: python import typer typer_app = typer.Typer() @typer_app.command() def foo(age: Annotated[int, typer.Argument(min=0)]): pass This works for a select few builtins, but the Typer solution doesn't abstract out validation properly. Why does the generic ``typer.Argument`` have fields that only have meaning if the annotated type is a number? The ``typer.Argument`` signature has a ridiculous number of fields that only apply for certain types. .. code-block:: python def Argument( # Parameter default: Optional[Any] = ..., *, callback: Optional[Callable[..., Any]] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # Custom type parser: Optional[Callable[[str], Any]] = None, # TyperArgument show_default: Union[bool, str] = True, show_choices: bool = True, show_envvar: bool = True, help: Optional[str] = None, hidden: bool = False, # Choice case_sensitive: bool = True, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime formats: Optional[List[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = "strict", lazy: Optional[bool] = None, atomic: bool = False, # Path 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: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... Cyclopts has an explicit :attr:`~.Parameter.validator` field that accepts a function: .. code-block:: python from cyclopts import App, parameter from typing import Annotated cyclopts_app = App() def age_validator(type_, value: int): if value < 0: raise ValueError @cyclopts_app.command() def foo(age: Annotated[int, Parameter(validator=age_validator)]): pass cyclopts_app() This solution is similar to how other libraries, like Attrs_ or Pydantic_, perform validation. Cyclopts has builtin validators for common use-cases. .. code-block:: python # Typer typer.Argument(file_okay=True, exists=True) # Cyclopts cyclopts.Parameter(validator=cyclopts.validators.Path(file_okay=True, exists=True)) .. _Attrs: https://www.attrs.org/en/stable/examples.html#validators .. _Pydantic: https://docs.pydantic.dev/latest/concepts/validators/ cyclopts-3.9.0/docs/source/vs_typer/version_flag/000077500000000000000000000000001475451620500221555ustar00rootroot00000000000000cyclopts-3.9.0/docs/source/vs_typer/version_flag/README.rst000066400000000000000000000034031475451620500236440ustar00rootroot00000000000000===================== Adding a Version Flag ===================== It's common to check a CLI app's version via a ``--version`` flag. Concretely, we want the following behavior: .. code-block:: console $ mypackage --version 1.2.3 To achieve this in Typer, we need the following `bulky implementation`_: .. code-block:: python import typer from typing import Annotated typer_app = typer.Typer() def version_callback(value: bool): if value: print("1.2.3") raise typer.Exit() @typer_app.callback() def common( version: Annotated[ bool, typer.Option( "--version", callback=version_callback, help="Print version.", ), ] = False, ): print("Callback body executed.") print("Typer:") typer_app(["--version"]) # 1.2.3 Not only is this a lot of boilerplate, but it also has some nasty side-effects, such as impacting `whether or not you need to specify the command in a single-command program.`_ On top of that, it's not very intuitive. Would you expect ``"Callback body executed."`` to be printed? When does ``version_callback`` get called? What is ``value``? With Cyclopts, the version is automatically detected by checking the version of the package instantiating :class:`App `. If you prefer explicitness, :attr:`~.App.version` can also be explicitly supplied to :class:`App `. .. code-block:: python import cyclopts cyclopts_app = cyclopts.App(version="1.2.3") cyclopts_app(["--version"]) # 1.2.3 .. _bulky implementation: https://github.com/tiangolo/typer/issues/52 .. _whether or not you need to specify the command in a single-command program.: ../default_command/README.html cyclopts-3.9.0/docs/source/vs_typer/version_flag/main.py000066400000000000000000000015601475451620500234550ustar00rootroot00000000000000from typing import Annotated import typer import cyclopts typer_app = typer.Typer() def version_callback(value: bool): if not value: return print(typer.__version__) raise typer.Exit() @typer_app.callback() def common( version: Annotated[ bool, typer.Option( "--version", "-v", callback=version_callback, help="Print version.", ), ] = False, ): print("Callback body executed.") print("Typer:") typer_app(["--version"], standalone_mode=False) # 0.9.0 # If ``version`` is not specified, Cyclopts will attempt to use # ``your_library.__version__`` based on the module ``App`` is instantiated in. # If the discovery fails, Cyclopts will fallback to ``0.0.0`` cyclopts_app = cyclopts.App(version=typer.__version__) print("Cyclopts:") cyclopts_app(["--version"]) # 0.9.0 cyclopts-3.9.0/poetry.lock000066400000000000000000003536651475451620500156120ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] name = "asttokens" version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] [package.extras] astroid = ["astroid (>=2,<4)"] test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "attrs" version = "25.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" files = [ {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "certifi" version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.6.11" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ {file = "coverage-7.6.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eafea49da254a8289bed3fab960f808b322eda5577cb17a3733014928bbfbebd"}, {file = "coverage-7.6.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a3f7cbbcb4ad95067a6525f83a6fc78d9cbc1e70f8abaeeaeaa72ef34f48fc3"}, {file = "coverage-7.6.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6b079b39246a7da9a40cfa62d5766bd52b4b7a88cf5a82ec4c45bf6e152306"}, {file = "coverage-7.6.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60d4ad09dfc8c36c4910685faafcb8044c84e4dae302e86c585b3e2e7778726c"}, {file = "coverage-7.6.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e433b6e3a834a43dae2889adc125f3fa4c66668df420d8e49bc4ee817dd7a70"}, {file = "coverage-7.6.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ac5d92e2cc121a13270697e4cb37e1eb4511ac01d23fe1b6c097facc3b46489e"}, {file = "coverage-7.6.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5128f3ba694c0a1bde55fc480090392c336236c3e1a10dad40dc1ab17c7675ff"}, {file = "coverage-7.6.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:397489c611b76302dfa1d9ea079e138dddc4af80fc6819d5f5119ec8ca6c0e47"}, {file = "coverage-7.6.11-cp310-cp310-win32.whl", hash = "sha256:c7719a5e1dc93883a6b319bc0374ecd46fb6091ed659f3fbe281ab991634b9b0"}, {file = "coverage-7.6.11-cp310-cp310-win_amd64.whl", hash = "sha256:c27df03730059118b8a923cfc8b84b7e9976742560af528242f201880879c1da"}, {file = "coverage-7.6.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:532fe139691af134aa8b54ed60dd3c806aa81312d93693bd2883c7b61592c840"}, {file = "coverage-7.6.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0b0f272901a5172090c0802053fbc503cdc3fa2612720d2669a98a7384a7bec"}, {file = "coverage-7.6.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4bda710139ea646890d1c000feb533caff86904a0e0638f85e967c28cb8eec50"}, {file = "coverage-7.6.11-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a165b09e7d5f685bf659063334a9a7b1a2d57b531753d3e04bd442b3cfe5845b"}, {file = "coverage-7.6.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff136607689c1c87f43d24203b6d2055b42030f352d5176f9c8b204d4235ef27"}, {file = "coverage-7.6.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:050172741de03525290e67f0161ae5f7f387c88fca50d47fceb4724ceaa591d2"}, {file = "coverage-7.6.11-cp311-cp311-win32.whl", hash = "sha256:27700d859be68e4fb2e7bf774cf49933dcac6f81a9bc4c13bd41735b8d26a53b"}, {file = "coverage-7.6.11-cp311-cp311-win_amd64.whl", hash = "sha256:cd4839813b09ab1dd1be1bbc74f9a7787615f931f83952b6a9af1b2d3f708bf7"}, {file = "coverage-7.6.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbb1a822fd858d9853333a7c95d4e70dde9a79e65893138ce32c2ec6457d7a36"}, {file = "coverage-7.6.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61c834cbb80946d6ebfddd9b393a4c46bec92fcc0fa069321fcb8049117f76ea"}, {file = "coverage-7.6.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a46d56e99a31d858d6912d31ffa4ede6a325c86af13139539beefca10a1234ce"}, {file = "coverage-7.6.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b48db06f53d1864fea6dbd855e6d51d41c0f06c212c3004511c0bdc6847b297"}, {file = "coverage-7.6.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6ff5be3b1853e0862da9d349fe87f869f68e63a25f7c37ce1130b321140f963"}, {file = "coverage-7.6.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be05bde21d5e6eefbc3a6de6b9bee2b47894b8945342e8663192809c4d1f08ce"}, {file = "coverage-7.6.11-cp312-cp312-win32.whl", hash = "sha256:e3b746fa0ffc5b6b8856529de487da8b9aeb4fb394bb58de6502ef45f3434f12"}, {file = "coverage-7.6.11-cp312-cp312-win_amd64.whl", hash = "sha256:ac476e6d0128fb7919b3fae726de72b28b5c9644cb4b579e4a523d693187c551"}, {file = "coverage-7.6.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c86f4c7a6d1a54a24d804d9684d96e36a62d3ef7c0d7745ae2ea39e3e0293251"}, {file = "coverage-7.6.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7eb0504bb307401fd08bc5163a351df301438b3beb88a4fa044681295bbefc67"}, {file = "coverage-7.6.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca95d40900cf614e07f00cee8c2fad0371df03ca4d7a80161d84be2ec132b7a4"}, {file = "coverage-7.6.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db4b1a69976b1b02acda15937538a1d3fe10b185f9d99920b17a740a0a102e06"}, {file = "coverage-7.6.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf96beb05d004e4c51cd846fcdf9eee9eb2681518524b66b2e7610507944c2f"}, {file = "coverage-7.6.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:08e5fb93576a6b054d3d326242af5ef93daaac9bb52bc25f12ccbc3fa94227cd"}, {file = "coverage-7.6.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25575cd5a7d2acc46b42711e8aff826027c0e4f80fb38028a74f31ac22aae69d"}, {file = "coverage-7.6.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8fa4fffd90ee92f62ff7404b4801b59e8ea8502e19c9bf2d3241ce745b52926c"}, {file = "coverage-7.6.11-cp313-cp313-win32.whl", hash = "sha256:0d03c9452d9d1ccfe5d3a5df0427705022a49b356ac212d529762eaea5ef97b4"}, {file = "coverage-7.6.11-cp313-cp313-win_amd64.whl", hash = "sha256:fd2fffc8ce8692ce540103dff26279d2af22d424516ddebe2d7e4d6dbb3816b2"}, {file = "coverage-7.6.11-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e7ac966ab110bd94ee844f2643f196d78fde1cd2450399116d3efdd706e19f5"}, {file = "coverage-7.6.11-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ba27a0375c5ef4d2a7712f829265102decd5ff78b96d342ac2fa555742c4f4f"}, {file = "coverage-7.6.11-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2778be4f574b39ec9dcd9e5e13644f770351ee0990a0ecd27e364aba95af89b"}, {file = "coverage-7.6.11-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5edc16712187139ab635a2e644cc41fc239bc6d245b16124045743130455c652"}, {file = "coverage-7.6.11-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6ff122a0a10a30121d9f0cb3fbd03a6fe05861e4ec47adb9f25e9245aabc19"}, {file = "coverage-7.6.11-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff562952f15eff27247a4c4b03e45ce8a82e3fb197de6a7c54080f9d4ba07845"}, {file = "coverage-7.6.11-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4f21e3617f48d683f30cf2a6c8b739c838e600cb1454fe6b2eb486ac2bce8fbd"}, {file = "coverage-7.6.11-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6d60577673ba48d8ae8e362e61fd4ad1a640293ffe8991d11c86f195479100b7"}, {file = "coverage-7.6.11-cp313-cp313t-win32.whl", hash = "sha256:13100f98497086b359bf56fc035a762c674de8ef526daa389ac8932cb9bff1e0"}, {file = "coverage-7.6.11-cp313-cp313t-win_amd64.whl", hash = "sha256:2c81e53782043b323bd34c7de711ed9b4673414eb517eaf35af92185b873839c"}, {file = "coverage-7.6.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff52b4e2ac0080c96e506819586c4b16cdbf46724bda90d308a7330a73cc8521"}, {file = "coverage-7.6.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f4679fcc9eb9004fdd1b00231ef1ec7167168071bebc4d66327e28c1979b4449"}, {file = "coverage-7.6.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90de4e9ca4489e823138bd13098af9ac8028cc029f33f60098b5c08c675c7bda"}, {file = "coverage-7.6.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c96a142057d83ee993eaf71629ca3fb952cda8afa9a70af4132950c2bd3deb9"}, {file = "coverage-7.6.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:476f29a258b9cd153f2be5bf5f119d670d2806363595263917bddc167d6e5cce"}, {file = "coverage-7.6.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:09d03f48d9025b8a6a116cddcb6c7b8ce80e4fb4c31dd2e124a7c377036ad58e"}, {file = "coverage-7.6.11-cp39-cp39-win32.whl", hash = "sha256:bb35ae9f134fbd9cf7302a9654d5a1e597c974202678082dcc569eb39a8cde03"}, {file = "coverage-7.6.11-cp39-cp39-win_amd64.whl", hash = "sha256:f382004fa4c93c01016d9226b9d696a08c53f6818b7ad59b4e96cb67e863353a"}, {file = "coverage-7.6.11-pp39.pp310-none-any.whl", hash = "sha256:adc2d941c0381edfcf3897f94b9f41b1e504902fab78a04b1677f2f72afead4b"}, {file = "coverage-7.6.11-py3-none-any.whl", hash = "sha256:f0f334ae844675420164175bf32b04e18a81fe57ad8eb7e0cfd4689d681ffed7"}, {file = "coverage-7.6.11.tar.gz", hash = "sha256:e642e6a46a04e992ebfdabed79e46f478ec60e2c528e1e1a074d63800eda4286"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "decorator" version = "5.1.1" description = "Decorators for Humans" optional = false python-versions = ">=3.5" files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] [[package]] name = "distlib" version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] name = "docstring-parser" version = "0.16" description = "Parse Python docstrings in reST, Google and Numpydoc format" optional = false python-versions = ">=3.6,<4.0" files = [ {file = "docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637"}, {file = "docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e"}, ] [[package]] name = "docutils" version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "executing" version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" files = [ {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, ] [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] [[package]] name = "filelock" version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "gitdb" version = "4.0.12" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, ] [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.44" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] [[package]] name = "identify" version = "2.6.5" description = "File identification library for Python" optional = false python-versions = ">=3.9" files = [ {file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"}, {file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "8.6.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "ipdb" version = "0.13.13" description = "IPython-enabled pdb" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, ] [package.dependencies] decorator = {version = "*", markers = "python_version > \"3.6\""} ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} [[package]] name = "ipython" version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" typing-extensions = {version = "*", markers = "python_version < \"3.10\""} [package.extras] all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "jedi" version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] parso = ">=0.8.4,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "line-profiler" version = "4.2.0" description = "Line-by-line profiler" optional = false python-versions = ">=3.8" files = [ {file = "line_profiler-4.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:70e2503f52ee6464ac908b578d73ad6dae21d689c95f2252fee97d7aa8426693"}, {file = "line_profiler-4.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b6047c8748d7a2453522eaea3edc8d9febc658b57f2ea189c03fe3d5e34595b5"}, {file = "line_profiler-4.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0048360a2afbd92c0b423f8207af1f6581d85c064c0340b0d02c63c8e0c8292c"}, {file = "line_profiler-4.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e71fa1c85f21e3de575c7c617fd4eb607b052cc7b4354035fecc18f3f2a4317"}, {file = "line_profiler-4.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5ec99d48cffdf36efbcd7297e81cc12bf2c0a7e0627a567f3ab0347e607b242"}, {file = "line_profiler-4.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bfc9582f19a64283434fc6a3fd41a3a51d59e3cce2dc7adc5fe859fcae67e746"}, {file = "line_profiler-4.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2b5dcfb3205e18c98c94388065f1604dc9d709df4dd62300ff8c5bbbd9bd163f"}, {file = "line_profiler-4.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:4999eb1db5d52cb34a5293941986eea4357fb9fe3305a160694e5f13c9ec4008"}, {file = "line_profiler-4.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:402406f200401a496fb93e1788387bf2d87c921d7f8f7e5f88324ac9efb672ac"}, {file = "line_profiler-4.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9a0b5696f1ad42bb31e90706e5d57845833483d1d07f092b66b4799847a2f76"}, {file = "line_profiler-4.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2f950fa19f797a9ab55c8d7b33a7cdd95c396cf124c3adbc1cf93a1978d2767"}, {file = "line_profiler-4.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d09fd8f580716da5a0b9a7f544a306b468f38eee28ba2465c56e0aa5d7d1822"}, {file = "line_profiler-4.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628f585960c6538873a9760d112db20b76b6035d3eaad7711a8bd80fa909d7ea"}, {file = "line_profiler-4.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:63ed929c7d41e230cc1c4838c25bbee165d7f2fa974ca28d730ea69e501fc44d"}, {file = "line_profiler-4.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6bda74fc206ba375396068526e9e7b5466a24c7e54cbd6ee1c98c1e0d1f0fd99"}, {file = "line_profiler-4.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:eaf6eb827c202c07b8b8d82363bb039a6747fbf84ca04279495a91b7da3b773f"}, {file = "line_profiler-4.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d29887f1226938a86db30ca3a125b1bde89913768a2a486fa14d0d3f8c0d91"}, {file = "line_profiler-4.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bf60706467203db0a872b93775a5e5902a02b11d79f8f75a8f8ef381b75789e1"}, {file = "line_profiler-4.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:934fd964eed9bed87e3c01e8871ee6bdc54d10edf7bf14d20e72f7be03567ae3"}, {file = "line_profiler-4.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d623e5b37fa48c7ad0c29b4353244346a5dcb1bf75e117e19400b8ffd3393d1b"}, {file = "line_profiler-4.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efcdbed9ba9003792d8bfd56c11bb3d4e29ad7e0d2f583e1c774de73bbf02933"}, {file = "line_profiler-4.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:df0149c191a95f2dbc93155b2f9faaee563362d61e78b8986cdb67babe017cdc"}, {file = "line_profiler-4.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5e3a1ca491a8606ed674882b59354087f6e9ab6b94aa6d5fa5d565c6f2acc7a8"}, {file = "line_profiler-4.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a85ff57d4ef9d899ca12d6b0883c3cab1786388b29d2fb5f30f909e70bb9a691"}, {file = "line_profiler-4.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:49db0804e9e330076f0b048d63fd3206331ca0104dd549f61b2466df0f10ecda"}, {file = "line_profiler-4.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2e983ed4fb2cd68bb8896f6bad7f29ddf9112b978f700448510477bc9fde18db"}, {file = "line_profiler-4.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d6b27c5880b29369e6bebfe434a16c60cbcd290aa4c384ac612e5777737893f8"}, {file = "line_profiler-4.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2584dc0af3107efa60bd2ccaa7233dca98e3dff4b11138c0ac30355bc87f1a"}, {file = "line_profiler-4.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6767d8b922a7368b6917a47c164c3d96d48b82109ad961ef518e78800947cef4"}, {file = "line_profiler-4.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3137672a769717be4da3a6e006c3bd7b66ad4a341ba89ee749ef96c158a15b22"}, {file = "line_profiler-4.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:727e970d358616a1a33d51d696efec932a5ef7730785df62658bd7e74aa58951"}, {file = "line_profiler-4.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8dd674be39b27920aeaaacb12df1f7e789cd60238972bf7caf0f352ce97bb502"}, {file = "line_profiler-4.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a0cbb5385021a793acb25bed1bcc1fe3f522092566e4f8dee71e5acde699deb"}, {file = "line_profiler-4.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e1240667d49d147b1f4b6e966fc9a0223fd58b126f0ee58c8b7a82dfee39ec07"}, {file = "line_profiler-4.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0a52df987b8d3a9b5ffb51f93171d2f4ba82cf8c384256bc8d13cbdbb3d3172"}, {file = "line_profiler-4.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4691399961e64646a1293831da4dcaa5908588a41d845f55ac708f7da600a4f"}, {file = "line_profiler-4.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c030eaf3f44c3dadf4c8d92bc9994afac2ca4d3ae90acd46272910de9f62a89"}, {file = "line_profiler-4.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:91c8078bd7d6c86ca148074bc1583ff4028165153ad8f5f84c6d5ed33d4a150e"}, {file = "line_profiler-4.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:9f63aa68533710dcdad665e641feff7392609299d54c399599768bcbbd3435eb"}, {file = "line_profiler-4.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:80dd7e7990e346ed8ef32702f8fe3c60abdb0de95980d422c02f1ef30a6a828d"}, {file = "line_profiler-4.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:31e1057448cfdb2678756163135b43bbbf698b2a1f7c88eb807f3fb2cdc2e3e7"}, {file = "line_profiler-4.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ea02ccd7dc97b5777c032297991b5637130fbd07fa2c6a1f89f248aa12ef71b"}, {file = "line_profiler-4.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4bbbc4e8545f0c187cfed7c323b8cc1121d28001b222b26f6bc3bc554ba82d4f"}, {file = "line_profiler-4.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d76d37c1084210363261d08eaabd30310eefb707ba8ab736a61e43930afaf47"}, {file = "line_profiler-4.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:22f84c3dbb807a26c115626bee19cb5f93683fa08c8d3836ec30af06fa9eb5c3"}, {file = "line_profiler-4.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e6131bcd5888371b61e05631555592feba12e73c96596b8d26ffe03cea0fc088"}, {file = "line_profiler-4.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fb58aa12cf64f0176d84bc4033bb0701fe8075d5da57149839ef895d961bbdad"}, {file = "line_profiler-4.2.0.tar.gz", hash = "sha256:09e10f25f876514380b3faee6de93fb0c228abba85820ba1a591ddb3eb451a96"}, ] [package.extras] all = ["Cython (>=3.0.3)", "IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.8.1)", "cmake (>=3.21.2)", "coverage[toml] (>=6.1.1)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=7.3.0)", "ninja (>=1.10.2)", "pytest (>=6.2.5)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest-cov (>=3.0.0)", "rich (>=12.3.0)", "scikit-build (>=0.11.1)", "setuptools (>=41.0.1)", "setuptools (>=68.2.2)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.3)"] all-strict = ["Cython (==3.0.3)", "IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.8.1)", "cmake (==3.21.2)", "coverage[toml] (==6.1.1)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==7.3.0)", "ninja (==1.10.2)", "pytest (==6.2.5)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest-cov (==3.0.0)", "rich (==12.3.0)", "scikit-build (==0.11.1)", "setuptools (==41.0.1)", "setuptools (==68.2.2)", "ubelt (==1.3.4)", "xdoctest (==1.1.3)"] ipython = ["IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)"] ipython-strict = ["IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)"] optional = ["IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "rich (>=12.3.0)"] optional-strict = ["IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "rich (==12.3.0)"] tests = ["coverage[toml] (>=6.1.1)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=7.3.0)", "pytest (>=6.2.5)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest-cov (>=3.0.0)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.3)"] tests-strict = ["coverage[toml] (==6.1.1)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==7.3.0)", "pytest (==6.2.5)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest-cov (==3.0.0)", "ubelt (==1.3.4)", "xdoctest (==1.1.3)"] [[package]] name = "linkify-it-py" version = "2.0.3" description = "Links recognition library with FULL unicode support." optional = false python-versions = ">=3.7" files = [ {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, ] [package.dependencies] uc-micro-py = "*" [package.extras] benchmark = ["pytest", "pytest-benchmark"] dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] doc = ["myst-parser", "sphinx", "sphinx-book-theme"] test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, ] [package.dependencies] traitlets = "*" [[package]] name = "mdit-py-plugins" version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, ] [package.dependencies] markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "myst-parser" version = "3.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.8" files = [ {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, ] [package.dependencies] docutils = ">=0.18,<0.22" jinja2 = "*" linkify-it-py = {version = ">=2.0,<3.0", optional = true, markers = "extra == \"linkify\""} markdown-it-py = ">=3.0,<4.0" mdit-py-plugins = ">=0.4,<1.0" pyyaml = "*" sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] [[package]] name = "packaging" version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] name = "parso" version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, ] [package.extras] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["docopt", "pytest"] [[package]] name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] ptyprocess = ">=0.5" [[package]] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "4.1.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] wcwidth = "*" [[package]] name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] [[package]] name = "pure-eval" version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, ] [package.extras] tests = ["pytest"] [[package]] name = "pydantic" version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, ] [package.dependencies] annotated-types = ">=0.6.0" pydantic-core = "2.27.2" typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] timezone = ["tzdata"] [[package]] name = "pydantic-core" version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-mock" version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, ] [package.dependencies] pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rich-rst" version = "1.3.1" description = "A beautiful reStructuredText renderer for rich" optional = false python-versions = ">=3.6" files = [ {file = "rich_rst-1.3.1-py3-none-any.whl", hash = "sha256:498a74e3896507ab04492d326e794c3ef76e7cda078703aa592d1853d91098c1"}, {file = "rich_rst-1.3.1.tar.gz", hash = "sha256:fad46e3ba42785ea8c1785e2ceaa56e0ffa32dbe5410dec432f37e4107c4f383"}, ] [package.dependencies] docutils = "*" rich = ">=12.0.0" [[package]] name = "smmap" version = "5.0.2" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" files = [ {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "sphinx" version = "7.4.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" files = [ {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] alabaster = ">=0.7.14,<0.8.0" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} docutils = ">=0.20,<0.22" imagesize = ">=1.3" importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-autodoc-typehints" version = "2.3.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.9" files = [ {file = "sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67"}, {file = "sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084"}, ] [package.dependencies] sphinx = ">=7.3.5" [package.extras] docs = ["furo (>=2024.1.29)"] numpy = ["nptyping (>=2.5)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.4.4)", "defusedxml (>=0.7.1)", "diff-cover (>=9)", "pytest (>=8.1.1)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.11)"] [[package]] name = "sphinx-copybutton" version = "0.5.2" description = "Add a copy button to each of your code cells." optional = false python-versions = ">=3.7" files = [ {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, ] [package.dependencies] sphinx = ">=1.8" [package.extras] code-style = ["pre-commit (==2.12.1)"] rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] [[package]] name = "sphinx-rtd-dark-mode" version = "1.3.0" description = "Dark mode for the Sphinx Read the Docs theme." optional = false python-versions = ">=3.4" files = [ {file = "sphinx_rtd_dark_mode-1.3.0-py3-none-any.whl", hash = "sha256:478da69c72a2a2ed7665c1f633cc612039f5801df416fd5f7c4820c2fe08c9c5"}, {file = "sphinx_rtd_dark_mode-1.3.0.tar.gz", hash = "sha256:0272bf3d9ef620921adc67e5634a66969419e744da84ea18830adacfdb160ea8"}, ] [package.dependencies] sphinx-rtd-theme = "*" [[package]] name = "sphinx-rtd-theme" version = "3.0.2" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.8" files = [ {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, ] [package.dependencies] docutils = ">0.18,<0.22" sphinx = ">=6,<9" sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "transifex-client", "twine", "wheel"] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, ] [package.dependencies] Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] [package.dependencies] asttokens = ">=2.1.0" executing = ">=1.2.0" pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "traitlets" version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "uc-micro-py" version = "1.0.3" description = "Micro subset of unicode data files for linkify-it-py projects." optional = false python-versions = ">=3.7" files = [ {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, ] [package.extras] test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "urllib3" version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [[package]] name = "zipp" version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] toml = ["tomli"] yaml = ["pyyaml"] [metadata] lock-version = "2.0" python-versions = ">=3.9" content-hash = "19ac0461c26b81bc4d21c4c9d638a929e6804cd95b54f9e02757656cf4b5b7b7" cyclopts-3.9.0/pyproject.toml000066400000000000000000000116601475451620500163140ustar00rootroot00000000000000[build-system] requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.1"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] enable = true vcs = "git" style = "semver" [tool.poetry] name = "cyclopts" version = "0.0.0" # Do not change, let poetry-dynamic-versioning handle it. homepage = "https://github.com/BrianPugh/cyclopts" repository = "https://github.com/BrianPugh/cyclopts" license = "Apache-2.0" description = "" authors = ["Brian Pugh"] readme = "README.md" packages = [{include = "cyclopts"}] include = ["cyclopts/*.so", "cyclopts/*.pyd"] # Compiled extensions [tool.poetry.build] generate-setup-file = false [tool.poetry.scripts] [tool.poetry.dependencies] # Be as loose as possible if writing a library. python = ">=3.9" typing-extensions = { version = ">=4.8.0", python = "<3.11" } importlib-metadata = { version = ">=4.4", python = '<3.10' } attrs = ">=23.1.0" rich = ">=13.6.0" docstring-parser = {version=">=0.15", python = "<4.0"} rich-rst = "^1.3.1" tomli = {version = ">=2.0.0", python = "<3.11", optional = true} pyyaml = {version = ">=6.0.1", optional = true} [tool.poetry.group.docs.dependencies] sphinx = "~7.4.7" sphinx_rtd_theme = ">=3.0.0,<4.0.0" gitpython = ">=3.1.31" sphinx-copybutton = "^0.5" myst-parser = {extras = ["linkify"], version = "^3.0.1"} sphinx-autodoc-typehints = ">=1.25.2,<3.0.0" sphinx-rtd-dark-mode = "^1.3.0" [tool.poetry.group.dev.dependencies] coverage = {extras = ["toml"], version = ">=5.1"} pre_commit = ">=2.16.0" pytest = ">=8.2.0" pytest-cov = ">=3.0.0" pytest-mock = ">=3.7.0" pydantic = "^2.10.6" toml = "^0.10.2" pyyaml = ">=6.0.1" [tool.poetry.group.debug] optional = true [tool.poetry.group.debug.dependencies] ipdb = ">=0.13.9" line_profiler = ">=3.5.1" [tool.poetry.extras] toml = ["tomli"] yaml = ["pyyaml"] [tool.coverage.run] branch = true omit = [ "tests/*", ] [tool.coverage.report] exclude_lines = [ # Have to re-enable the standard pragma "pragma: no cover", # Don't complain about missing debug-only code: "def __repr__", "if self.debug:", "if debug:", "if DEBUG:", # Don't complain if tests don't hit defensive assertion code: "raise AssertionError", "raise NotImplementedError", "raise TypeError", # Don't complain if non-runnable code isn't run: "if 0:", "if False:", "if __name__ == .__main__.:", "if TYPE_CHECKING:", "if sys.version_info < (3, 10):", "if sys.version_info >= (3, 10):", "from typing import Annotated", "except ImportError:", # Overloads can't have coverage: "@overload", ] omit = [ "cyclopts/protocols.py" ] [tool.pyright] venvPath = "." venv = ".venv" ignore = ["docs/", "tests/test_py3*.py"] [tool.ruff] target-version = 'py38' line-length = 120 exclude = [ "migrations", "__pycache__", "manage.py", "settings.py", "env", ".env", "venv", ".venv", ] [tool.ruff.format] docstring-code-format = true [tool.ruff.lint] select = [ "B", # flake8-bugbear "C4", # flake8-comprehensions "D", # pydocstyle "E", # Error "F", # pyflakes "I", # isort "N", # pep8-naming "PGH", # pygrep-hooks "PTH", # flake8-use-pathlib "Q", # flake8-quotes "TRY", # tryceratops "UP", # pyupgrade "W", # Warning "YTT", # flake8-2020 ] ignore = [ "B905", # zip strict=True; remove once python <3.10 support is dropped. "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D200", "D401", "E402", "E501", "PGH003", # Use specific rule codes when ignoring type issues "TRY003", # Avoid specifying messages outside exception class; overly strict, especially for ValueError "TRY300", # Consider moving this statement to an `else` block ] [tool.ruff.lint.flake8-bugbear] extend-immutable-calls = [ "chr", "typer.Argument", "typer.Option", ] [tool.ruff.lint.pydocstyle] convention = "numpy" [tool.ruff.lint.per-file-ignores] "tests/*.py" = [ "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D205", "D400", "D404", "S102", # use of "exec" "S106", # possible hardcoded password. "PGH001", # use of "eval" ] "docs/*.py" = [ "F811", # redefinition ] "cyclopts/config/*.py" = [ "PTH123", # `open()` should be replaced by `Path.open()`. Pyright doesn't understand that it must be a Path. ] [tool.ruff.lint.pep8-naming] staticmethod-decorators = [ "pydantic.validator", "pydantic.root_validator", ] [tool.codespell] skip = 'poetry.lock,' [tool.creosote] venvs=[".venv"] paths=["cyclopts"] deps-file="pyproject.toml" sections=["tool.poetry.dependencies"] exclude-deps =[ "importlib-metadata", "typing-extensions", "docstring-parser", # Not detected due to deferred import. "rich-rst", # Not detected due to deferred import. "rich", # Not detected due to deferred import. "tomli", # Not detected due to optional feature. ] cyclopts-3.9.0/tests/000077500000000000000000000000001475451620500145365ustar00rootroot00000000000000cyclopts-3.9.0/tests/apps/000077500000000000000000000000001475451620500155015ustar00rootroot00000000000000cyclopts-3.9.0/tests/apps/README.md000066400000000000000000000002631475451620500167610ustar00rootroot00000000000000# Test Apps Each file in this folder contains a (more or less) complete application aimed at testing cyclopts in "real life" situations. Essentially, these are integration tests. cyclopts-3.9.0/tests/apps/config.toml000066400000000000000000000001011475451620500176330ustar00rootroot00000000000000[create.burger] mayo=false custom=["sweet-chili", "house-sauce"] cyclopts-3.9.0/tests/apps/draw.py000066400000000000000000000061641475451620500170170ustar00rootroot00000000000000from dataclasses import KW_ONLY, dataclass # pyright: ignore[reportAttributeAccessIssue] from pathlib import Path from typing import Annotated, Literal, NamedTuple, Union import cyclopts from cyclopts import App, Parameter from cyclopts.types import UInt8 toml_path = Path("draw.toml") toml_data = """\ [tool.draw] units = "meters" no_exit_on_error = false [tool.draw.line] units = "feet" """ toml_path.write_text(toml_data) app = App( help="Demo drawing app.", config=( cyclopts.config.Toml(toml_path, root_keys=("tool", "draw")), cyclopts.config.Toml(toml_path, root_keys=("tool", "draw"), use_commands_as_keys=False), ), ) class Coordinate(NamedTuple): x: float "X coordinate." y: float "Y coordinate." @Parameter(name="*") @dataclass class Config: _: KW_ONLY units: Literal["meters", "feet"] = "meters" "Drawing units." color: tuple[UInt8, UInt8, UInt8] = (0x00, 0x00, 0x00) "RGB uint8 triple." @app.command def line( start: Coordinate, end: Coordinate, *, config: Config, ): """Draw a line. Parameters ---------- start: Coordinate Start of line. end: Coordinate End of line. """ print(f"Drawing a line with from {start} to {end} {config.units} in {config.color=}.") @app.command def elliptic_curve( start_point: Coordinate, end_point: Coordinate, r1: float, r2: float, *, config: Config, ): """Draw a elliptical curve.""" @app.command def circle( center: Coordinate, radius: Union[Literal["unit"], float], *, config: Config, ): """Draw a circle. Parameters ---------- center: Literal["origin"] | Coordinate Center of the circle to be drawn. center.x: float Circle center's X position. center.y: float Circle center's Y position. radius: float Radius of the circle. """ if radius == "unit": radius = 1.0 print(f"Drawing a circle with {radius=} {config.units} at {center=}") @app.command def polygon(*vertices: Annotated[Coordinate, Parameter(required=True)], config: Config): """Draw a polygon. Parameters ---------- vertices: Coordinate List of (x, y) coordinates that make up the polygon. """ print(f"Drawing a polygon with {vertices=} {config.units} in {config.color=}.") @app.command def polygon2(vertices: list[Coordinate], /, *, config: Config): """Draw a polygon (alternative implementation). Parameters ---------- vertices: Coordinate List of (x, y) coordinates that make up the polygon. """ print(f"Drawing a polygon with {vertices=} {config.units} in {config.color=}.") @app.meta.default def meta( *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], no_exit_on_error: Annotated[bool, Parameter(negative=())] = False, ): """ Parameters ---------- exit_on_error: bool Exit on error. """ app(tokens, exit_on_error=not no_exit_on_error, print_error=not no_exit_on_error) toml_path.unlink() if __name__ == "__main__": app.meta(print_error=False, exit_on_error=False) cyclopts-3.9.0/tests/apps/test_burgery.py000066400000000000000000000123111475451620500205670ustar00rootroot00000000000000from pathlib import Path from textwrap import dedent from typing import Annotated, List, Literal, Optional import cyclopts from cyclopts import App, Parameter, validators config_file = Path(__file__).parent / "config.toml" app = App( name="burgery", help="Welcome to Cyclopts Burgery!", config=cyclopts.config.Toml(config_file), ) app.command(create := App(name="create")) @create.command def burger( variety: Literal["classic", "double"], quantity: Annotated[int, Parameter(validator=validators.Number(gt=0))] = 1, /, *, lettuce: Annotated[bool, Parameter(name="--iceberg", group="Toppings")] = True, tomato: Annotated[bool, Parameter(group="Toppings")] = True, onion: Annotated[bool, Parameter(group="Toppings")] = True, mustard: Annotated[bool, Parameter(group="Condiments")] = True, ketchup: Annotated[bool, Parameter(group="Condiments")] = True, mayo: Annotated[bool, Parameter(group="Condiments")] = True, custom: Annotated[Optional[List[str]], Parameter(group="Condiments")] = None, ): """Create a burger. Parameters ---------- variety: Literal["classic", "double"] Type of burger to create quantity: int lettuce: bool Add lettuce. tomato: bool Add tomato. onion: bool Add onion. mustard: bool Add mustard. ketchup: bool Add ketchup. """ return locals() def test_create_burger_help(console): with console.capture() as capture: app("create burger --help", console=console) actual = capture.get() expected = dedent( """\ Usage: burgery create burger [ARGS] [OPTIONS] Create a burger. ╭─ Arguments ────────────────────────────────────────────────────────╮ │ * VARIETY Type of burger to create [choices: classic, double] │ │ [required] │ │ QUANTITY [default: 1] │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Condiments ───────────────────────────────────────────────────────╮ │ --mustard --no-mustard Add mustard. [default: True] │ │ --ketchup --no-ketchup Add ketchup. [default: True] │ │ --mayo --no-mayo [default: True] │ │ --custom --empty-custom │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Toppings ─────────────────────────────────────────────────────────╮ │ --iceberg --no-iceberg Add lettuce. [default: True] │ │ --tomato --no-tomato Add tomato. [default: True] │ │ --onion --no-onion Add onion. [default: True] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_create_burger_1(): """Tests generic functionality. Detailed: * that config-file overrides (mayo, custom) work. * typical boolean flags work. """ actual = app("create burger classic --iceberg --no-onion --no-ketchup --custom sriracha --custom egg") assert actual == { "variety": "classic", "quantity": 1, "lettuce": True, "tomato": True, "onion": False, "ketchup": False, "mustard": True, "mayo": False, # Set from config file. "custom": ["sriracha", "egg"], } def test_create_burger_2(): """Tests that the list from the toml file correctly populates.""" actual = app("create burger classic") assert actual == { "variety": "classic", "quantity": 1, "lettuce": True, "tomato": True, "onion": True, "ketchup": True, "mustard": True, "mayo": False, # Set from config file. "custom": ["sweet-chili", "house-sauce"], # Set from config file. } def test_create_burger_3(): """Tests the --empty- config override.""" actual = app("create burger classic --empty-custom") assert actual == { "variety": "classic", "quantity": 1, "lettuce": True, "tomato": True, "onion": True, "ketchup": True, "mustard": True, "mayo": False, # Set from config file. "custom": [], } if __name__ == "__main__": app() cyclopts-3.9.0/tests/config/000077500000000000000000000000001475451620500160035ustar00rootroot00000000000000cyclopts-3.9.0/tests/config/test_common.py000066400000000000000000000211051475451620500207030ustar00rootroot00000000000000from dataclasses import dataclass from pathlib import Path from typing import Annotated, Any, Dict import pytest from cyclopts.argument import ArgumentCollection, Token from cyclopts.config._common import ConfigFromFile from cyclopts.exceptions import CycloptsError from cyclopts.parameter import Parameter class DummyErrorConfigNoMsg(ConfigFromFile): def _load_config(self, path: Path) -> Dict[str, Any]: raise ValueError class DummyErrorConfigMsg(ConfigFromFile): def _load_config(self, path: Path) -> Dict[str, Any]: raise ValueError("My exception's message.") class Dummy(ConfigFromFile): def _load_config(self, path: Path) -> Dict[str, Any]: return { "key1": "foo1", "key2": "foo2", "function1": { "key1": "bar1", "key2": "bar2", }, "meta_param": 123, } class DummyRootKeys(ConfigFromFile): def _load_config(self, path: Path) -> Dict[str, Any]: return { "tool": { "cyclopts": { "key1": "foo1", "key2": "foo2", "function1": { "key1": "bar1", "key2": "bar2", }, } } } class DummySubKeys(ConfigFromFile): def _load_config(self, path: Path) -> Dict[str, Any]: return { "key1": { "subkey1": ["subkey1val1", "subkey1val2"], "subkey2": ["subkey2val1", "subkey2val2"], }, "key2": "foo2", } def function1(key1, key2): pass @pytest.fixture def config(tmp_path): return Dummy(tmp_path / "cyclopts-config-test-file.dummy") @pytest.fixture def config_root_keys(tmp_path): return DummyRootKeys(tmp_path / "cyclopts-config-test-file.dummy") @pytest.fixture def config_sub_keys(tmp_path): return DummySubKeys(tmp_path / "cyclopts-config-test-file.dummy") @pytest.fixture def argument_collection(): def foo(key1, key2): pass out = ArgumentCollection._from_callable(foo) out[0].append(Token(keyword="--key1", value="cli1", source="cli")) return out @pytest.fixture def apps(app): @app.command def function1(): pass @app.meta.default def meta( *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], meta_param: Annotated[int, Parameter(negative=())] = 42, ): pass return [app] def test_config_common_root_keys_empty(apps, config, argument_collection): config.path.touch() config(apps, (), argument_collection) assert argument_collection[0].tokens == [Token(keyword="--key1", value="cli1", source="cli")] assert argument_collection[1].tokens == [Token(keyword="[key2]", value="foo2", source=str(config.path))] def test_config_common_root_keys_populated(apps, config_root_keys, argument_collection): config_root_keys.path.touch() config_root_keys.root_keys = ["tool", "cyclopts"] config_root_keys(apps, (), argument_collection) assert argument_collection[0].tokens == [Token(keyword="--key1", value="cli1", source="cli")] assert argument_collection[1].tokens == [ Token(keyword="[tool][cyclopts][key2]", value="foo2", source=str(config_root_keys.path)) ] def test_config_common_must_exist_false(config, mocker): """If ``must_exist==False``, then the specified file is allowed to not exist. If the file does not exist, then have an empty config. """ spy_load_config = mocker.spy(config, "_load_config") config.must_exist = False _ = config.config # does NOT raise a FileNotFoundError assert config.config == {} spy_load_config.assert_not_called() def test_config_common_must_exist_true(config): """If ``must_exist==True``, then the specified file must exist.""" config.must_exist = True with pytest.raises(FileNotFoundError): _ = config.config @pytest.mark.parametrize("must_exist", [True, False]) def test_config_common_search_parents_true_exists(tmp_path, must_exist, config, mocker): """Tests finding an existing parent.""" spy_load_config = mocker.spy(config, "_load_config") original_path = config.path original_path.touch() config.path = tmp_path / "folder1" / "folder2" / "folder3" / "folder4" / config.path.name config.must_exist = must_exist config.search_parents = True _ = config.config spy_load_config.assert_called_once_with(original_path) def test_config_common_must_exist_true_search_parents_true_missing(tmp_path, config, mocker): """Tests finding a missing parent.""" spy_load_config = mocker.spy(config, "_load_config") config.path = tmp_path / "folder1" / "folder2" / "folder3" / "folder4" / config.path.name config.must_exist = True config.search_parents = True with pytest.raises(FileNotFoundError): _ = config.config spy_load_config.assert_not_called() def test_config_common_must_exist_false_search_parents_true_missing(tmp_path, config, mocker): """Tests finding a missing parent.""" spy_load_config = mocker.spy(config, "_load_config") config.path = tmp_path / "folder1" / "folder2" / "folder3" / "folder4" / config.path.name config.must_exist = False config.search_parents = True assert config.config == {} spy_load_config.assert_not_called() def test_config_common_kwargs(apps, config): config.path.touch() def foo(key1, **kwargs): pass argument_collection = ArgumentCollection._from_callable(foo) config(apps, (), argument_collection) # Don't parse ``kwargs`` from config. assert argument_collection[-1].tokens == [ Token(keyword="[key2]", value="foo2", source=str(config.path.absolute()), index=0, keys=("key2",)), ] def test_config_common_subkeys(apps, config_sub_keys): config_sub_keys.path.touch() @dataclass class Example: subkey1: list[str] subkey2: list[str] def foo(key1: Example, key2): pass argument_collection = ArgumentCollection._from_callable(foo) config_sub_keys(apps, (), argument_collection) assert len(argument_collection) == 4 assert len(argument_collection[0].tokens) == 0 assert len(argument_collection[1].tokens) == 2 assert argument_collection[1].tokens[0].keyword == "[key1][subkey1]" assert argument_collection[1].tokens[0].value == "subkey1val1" assert argument_collection[1].tokens[0].index == 0 assert argument_collection[1].tokens[0].keys == () assert argument_collection[1].tokens[0].source.endswith("cyclopts-config-test-file.dummy") assert argument_collection[1].tokens[1].keyword == "[key1][subkey1]" assert argument_collection[1].tokens[1].value == "subkey1val2" assert argument_collection[1].tokens[1].index == 1 assert argument_collection[1].tokens[1].keys == () assert argument_collection[1].tokens[1].source.endswith("cyclopts-config-test-file.dummy") assert len(argument_collection[2].tokens) == 2 assert argument_collection[2].tokens[0].keyword == "[key1][subkey2]" assert argument_collection[2].tokens[0].value == "subkey2val1" assert argument_collection[2].tokens[0].index == 0 assert argument_collection[2].tokens[0].keys == () assert argument_collection[2].tokens[0].source.endswith("cyclopts-config-test-file.dummy") assert argument_collection[2].tokens[1].keyword == "[key1][subkey2]" assert argument_collection[2].tokens[1].value == "subkey2val2" assert argument_collection[2].tokens[1].index == 1 assert argument_collection[2].tokens[1].keys == () assert argument_collection[2].tokens[1].source.endswith("cyclopts-config-test-file.dummy") assert len(argument_collection[3].tokens) == 1 assert argument_collection[3].tokens[0].keyword == "[key2]" assert argument_collection[3].tokens[0].value == "foo2" assert argument_collection[3].tokens[0].index == 0 assert argument_collection[3].tokens[0].keys == () assert argument_collection[3].tokens[0].source.endswith("cyclopts-config-test-file.dummy") def test_config_exception_during_load_config_no_msg(tmp_path): path = tmp_path / "config" path.touch() dummy_error_config = DummyErrorConfigNoMsg(path) with pytest.raises(CycloptsError) as e: _ = dummy_error_config.config assert str(e.value) == "ValueError" def test_config_exception_during_load_config_msg(tmp_path): path = tmp_path / "config" path.touch() dummy_error_config = DummyErrorConfigMsg(path) with pytest.raises(CycloptsError) as e: _ = dummy_error_config.config assert str(e.value) == "ValueError: My exception's message." cyclopts-3.9.0/tests/config/test_end2end.py000066400000000000000000000013731475451620500207370ustar00rootroot00000000000000from textwrap import dedent from cyclopts.config import Toml def test_config_end2end(app, tmp_path, assert_parse_args): config_fn = tmp_path / "config.toml" config_fn.write_text( dedent( """\ [tool.cyclopts] key1 = "foo1" key2 = "foo2" [tool.cyclopts.function1] key3 = "bar1" key4 = "bar2" """ ) ) app.config = Toml(config_fn, root_keys=["tool", "cyclopts"]) @app.default def default(key1, key2): pass @app.command def function1(key3, key4): pass assert_parse_args(default, "foo", key1="foo", key2="foo2") assert_parse_args(function1, "function1 --key4=fizz", key3="bar1", key4="fizz") cyclopts-3.9.0/tests/config/test_env.py000066400000000000000000000102741475451620500202100ustar00rootroot00000000000000from dataclasses import dataclass import pytest from cyclopts.argument import ArgumentCollection from cyclopts.config import Env from cyclopts.token import Token @pytest.fixture def apps(): """App is only used as a dictionary in these tests.""" return [{"function1": None}] def test_config_env_default(apps, monkeypatch): def foo(bar: int): pass argument_collection = ArgumentCollection._from_callable(foo) monkeypatch.setenv("CYCLOPTS_TEST_APP_BAR", "100") monkeypatch.setenv("CYCLOPTS_TEST_APP_SOMETHING_ELSE", "100") Env("CYCLOPTS_TEST_APP_", command=False)(apps, (), argument_collection) assert len(argument_collection[0].tokens) == 1 assert argument_collection[0].tokens[0].keyword == "CYCLOPTS_TEST_APP_BAR" assert argument_collection[0].tokens[0].value == "100" assert argument_collection[0].tokens[0].source == "env" assert argument_collection[0].tokens[0].index == 0 assert argument_collection[0].tokens[0].keys == () def test_config_env_default_already_populated(apps, monkeypatch): def foo(bar: int): pass argument_collection = ArgumentCollection._from_callable(foo) argument_collection[0].append(Token(keyword="--bar", value="500", source="cli")) monkeypatch.setenv("CYCLOPTS_TEST_APP_BAR", "100") monkeypatch.setenv("CYCLOPTS_TEST_APP_SOMETHING_ELSE", "100") Env("CYCLOPTS_TEST_APP_", command=False)(apps, (), argument_collection) assert len(argument_collection[0].tokens) == 1 assert argument_collection[0].tokens[0].keyword == "--bar" assert argument_collection[0].tokens[0].value == "500" assert argument_collection[0].tokens[0].source == "cli" assert argument_collection[0].tokens[0].index == 0 assert argument_collection[0].tokens[0].keys == () def test_config_env_command_true(apps, monkeypatch): def foo(bar: int): pass argument_collection = ArgumentCollection._from_callable(foo) monkeypatch.setenv("CYCLOPTS_TEST_APP_FOO_BAR", "100") Env("CYCLOPTS_TEST_APP_", command=True)(apps, ("foo",), argument_collection) assert len(argument_collection[0].tokens) == 1 assert argument_collection[0].tokens[0].keyword == "CYCLOPTS_TEST_APP_FOO_BAR" assert argument_collection[0].tokens[0].value == "100" assert argument_collection[0].tokens[0].source == "env" assert argument_collection[0].tokens[0].index == 0 assert argument_collection[0].tokens[0].keys == () def test_config_env_dict(apps, monkeypatch): def foo(bar_bar: dict): pass ac = ArgumentCollection._from_callable(foo) monkeypatch.setenv("CYCLOPTS_TEST_APP_BAR_BAR_BUZZ", "100") monkeypatch.setenv("CYCLOPTS_TEST_APP_BAR_BAR_FIZZ", "200") Env("CYCLOPTS_TEST_APP_", command=False)(apps, (), ac) assert len(ac[0].tokens) == 2 assert ac[0].tokens[0].keyword == "CYCLOPTS_TEST_APP_BAR_BAR_BUZZ" assert ac[0].tokens[0].value == "100" assert ac[0].tokens[0].source == "env" assert ac[0].tokens[0].index == 0 assert ac[0].tokens[0].keys == ("buzz",) assert ac[0].tokens[1].keyword == "CYCLOPTS_TEST_APP_BAR_BAR_FIZZ" assert ac[0].tokens[1].value == "200" assert ac[0].tokens[1].source == "env" assert ac[0].tokens[1].index == 0 assert ac[0].tokens[1].keys == ("fizz",) def test_config_env_dataclass(apps, monkeypatch): @dataclass class User: fizz_fizz: int buzz_buzz: int def foo(bar_bar: User): pass ac = ArgumentCollection._from_callable(foo) monkeypatch.setenv("CYCLOPTS_TEST_APP_BAR_BAR_BUZZ_BUZZ", "100") monkeypatch.setenv("CYCLOPTS_TEST_APP_BAR_BAR_FIZZ_FIZZ", "200") Env("CYCLOPTS_TEST_APP_", command=False)(apps, (), ac) assert len(ac) == 3 assert len(ac[1].tokens) == 1 assert len(ac[2].tokens) == 1 assert ac[1].tokens[0].keyword == "CYCLOPTS_TEST_APP_BAR_BAR_FIZZ_FIZZ" assert ac[1].tokens[0].value == "200" assert ac[1].tokens[0].source == "env" assert ac[1].tokens[0].index == 0 assert ac[1].tokens[0].keys == () assert ac[2].tokens[0].keyword == "CYCLOPTS_TEST_APP_BAR_BAR_BUZZ_BUZZ" assert ac[2].tokens[0].value == "100" assert ac[2].tokens[0].source == "env" assert ac[2].tokens[0].index == 0 assert ac[2].tokens[0].keys == () cyclopts-3.9.0/tests/config/test_json.py000066400000000000000000000067671475451620500204050ustar00rootroot00000000000000import json from pathlib import Path from textwrap import dedent import pytest from cyclopts import App, CycloptsError from cyclopts.config._json import Json def test_config_json(tmp_path): fn = tmp_path / "test.yaml" fn.write_text( dedent( """\ { "foo": { "key1": "foo1", "key2": "foo2", "function1": { "key1": "bar1", "key2": "bar2" } } } """ ) ) config = Json(fn) assert config.config == { "foo": { "key1": "foo1", "key2": "foo2", "function1": { "key1": "bar1", "key2": "bar2", }, } } """ Test file-caching and chdir after app has been instantiated. See discussion: https://github.com/BrianPugh/cyclopts/issues/309 """ app = App(config=Json("config.json")) @app.command def create(name: str, age: int): print(f"{name} is {age} years old.") @pytest.fixture(autouse=True) def chdir_to_tmp_path(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) @pytest.fixture def config_path(tmp_path): return tmp_path / "config.json" def test_config_1(config_path, capsys, mocker): with config_path.open("w") as f: json.dump({"create": {"name": "Alice", "age": 30}}, f) json_config = app.config[0] spy_load_config = mocker.patch.object(json_config, "_load_config", wraps=json_config._load_config) # pyright: ignore[reportAttributeAccessIssue] app("create") assert capsys.readouterr().out == "Alice is 30 years old.\n" assert spy_load_config.call_count == 1 # Ensure that it doesn't get called again because the file hasn't changed. app("create") assert capsys.readouterr().out == "Alice is 30 years old.\n" assert spy_load_config.call_count == 1 # If we modify the file, then it should get loaded again. with config_path.open("w") as f: json.dump({"create": {"name": "Bob", "age": 40}}, f) app("create") assert capsys.readouterr().out == "Bob is 40 years old.\n" assert spy_load_config.call_count == 2 def test_config_2(config_path, capsys): with config_path.open("w") as f: json.dump({"create": {"name": "Bob", "age": 40}}, f) app("create") assert capsys.readouterr().out == "Bob is 40 years old.\n" def test_config_invalid_json(tmp_path, console): Path("config.json").write_text('{"this is": broken}') with pytest.raises(CycloptsError), console.capture() as capture: app("create", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ JSONDecodeError: │ │ {"this is": broken} │ │ ^ │ │ Expecting value: line 1 column 13 (char 12) │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected cyclopts-3.9.0/tests/config/test_toml.py000066400000000000000000000030571475451620500203740ustar00rootroot00000000000000from textwrap import dedent from typing import Annotated import pytest from cyclopts import App, Parameter from cyclopts.config import Toml def test_config_toml(tmp_path): fn = tmp_path / "test.toml" fn.write_text( dedent( """\ [foo] key1 = "foo1" key2 = "foo2" [foo.function1] key1 = "bar1" key2 = "bar2" """ ) ) config = Toml(fn) assert config.config == { "foo": { "key1": "foo1", "key2": "foo2", "function1": { "key1": "bar1", "key2": "bar2", }, } } @pytest.fixture def config_path(tmp_path): """Path to JSON configuration file in tmp_path""" return tmp_path / "config.toml" # same name that was provided to cyclopts.config.Json def test_duplicate_config_toml_with_meta(config_path): config_path.write_text( dedent( """\ [this-test] name = "Alice" """ ) ) app = App( config=( Toml("config.toml", root_keys=("this-test",)), Toml("config.toml", root_keys=("this-test",)), # Duplicate configs should still work. ), ) @app.meta.default def meta( *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], ): return app(tokens, exit_on_error=False) @app.default def main(name: str): return name assert app.meta([], exit_on_error=False) == "Alice" cyclopts-3.9.0/tests/config/test_yaml.py000066400000000000000000000011621475451620500203560ustar00rootroot00000000000000from textwrap import dedent from cyclopts.config._yaml import Yaml def test_config_yaml(tmp_path): fn = tmp_path / "test.yaml" fn.write_text( dedent( """\ foo: key1: foo1 key2: foo2 function1: key1: bar1 key2: bar2 """ ) ) config = Yaml(fn) assert config.config == { "foo": { "key1": "foo1", "key2": "foo2", "function1": { "key1": "bar1", "key2": "bar2", }, } } cyclopts-3.9.0/tests/conftest.py000066400000000000000000000047121475451620500167410ustar00rootroot00000000000000import sys from pathlib import Path import pytest from rich.console import Console import cyclopts import cyclopts.utils from cyclopts import Group, Parameter def pytest_ignore_collect(collection_path): for minor in range(8, 20): if sys.version_info < (3, minor) and collection_path.stem.startswith(f"test_py3{minor}_"): return True @pytest.fixture(autouse=True) def chdir_to_tmp_path(tmp_path, monkeypatch): """Automatically change current directory to tmp_path""" monkeypatch.chdir(tmp_path) @pytest.fixture def app(): return cyclopts.App() @pytest.fixture def console(): return Console(width=70, force_terminal=True, highlight=False, color_system=None, legacy_windows=False) @pytest.fixture def default_function_groups(): return (Parameter(), Group("Arguments"), Group("Parameters")) @pytest.fixture def assert_parse_args(app): def inner(f, cmd: str, *args, **kwargs): signature = cyclopts.utils.signature(f) expected_bind = signature.bind(*args, **kwargs) actual_command, actual_bind, _ = app.parse_args(cmd, print_error=False, exit_on_error=False) assert actual_command == f assert actual_bind == expected_bind return inner @pytest.fixture def assert_parse_args_config(app): def inner(config: dict, f, cmd: str, *args, **kwargs): signature = cyclopts.utils.signature(f) expected_bind = signature.bind(*args, **kwargs) actual_command, actual_bind, _ = app.parse_args(cmd, print_error=False, exit_on_error=False, **config) assert actual_command == f assert actual_bind == expected_bind return inner @pytest.fixture def assert_parse_args_partial(app): def inner(f, cmd: str, *args, **kwargs): signature = cyclopts.utils.signature(f) expected_bind = signature.bind_partial(*args, **kwargs) actual_command, actual_bind, _ = app.parse_args(cmd, print_error=False, exit_on_error=False) assert actual_command == f assert actual_bind == expected_bind return inner @pytest.fixture def convert(): """Function that performs a conversion for a given type/cmd pair. Goes through the whole app stack. """ def inner(type_, cmd): app = cyclopts.App() if isinstance(cmd, Path): cmd = cmd.as_posix() @app.default def target(arg1: type_): # pyright: ignore return arg1 return app(cmd, exit_on_error=False) return inner cyclopts-3.9.0/tests/test_annotations.py000066400000000000000000000042541475451620500205110ustar00rootroot00000000000000import inspect from collections import namedtuple from typing import Annotated, Any, Dict, List, Literal, Optional, Union import pytest from cyclopts.annotations import contains_hint, get_hint_name, resolve def test_resolve_annotated(): type_ = Annotated[Literal["foo", "bar"], "fizz"] res = resolve(type_) assert res == Literal["foo", "bar"] def test_resolve_empty(): res = resolve(inspect.Parameter.empty) assert res is str def test_get_hint_name_string(): assert get_hint_name("str") == "str" def test_get_hint_name_any(): assert get_hint_name(Any) == "Any" def test_get_hint_name_union(): assert get_hint_name(Union[int, str]) == "int|str" def test_get_hint_name_class_with_name(): class TestClass: pass assert get_hint_name(TestClass) == "TestClass" def test_get_hint_name_typing_with_name(): assert get_hint_name(List) == "list" def test_get_hint_name_generic_type(): assert get_hint_name(List[int]) == "list[int]" def test_get_hint_name_nested_generic_type(): assert get_hint_name(Dict[str, List[int]]) == "dict[str, list[int]]" def test_get_hint_name_optional_type(): assert get_hint_name(Optional[int]) == "int|None" def test_get_hint_name_namedtuple(): TestTuple = namedtuple("TestTuple", ["field1", "field2"]) assert get_hint_name(TestTuple) == "TestTuple" def test_get_hint_name_complex_union(): complex_type = Union[int, str, List[Dict[str, Any]]] assert get_hint_name(complex_type) == "int|str|list[dict[str, Any]]" def test_get_hint_name_fallback_str(): class NoNameClass: def __str__(self): return "NoNameClass" assert get_hint_name(NoNameClass()) == "NoNameClass" class CustomStr(str): """Dummy subclass of ``str``.""" @pytest.mark.parametrize( "hint,target_type,expected", [ (str, str, True), (CustomStr, str, True), (Union[int, str], str, True), (Annotated[Union[int, str], 1], str, True), (Annotated[Union[Annotated[int, 1], Annotated[str, 1]], 1], str, True), (int, str, False), ], ) def test_contains_hint(hint, target_type, expected): assert contains_hint(hint, target_type) == expected cyclopts-3.9.0/tests/test_app_name_derivation.py000066400000000000000000000011471475451620500221560ustar00rootroot00000000000000import pytest from cyclopts import App @pytest.fixture def mock_get_root_module_name(mocker): return mocker.patch("cyclopts.core._get_root_module_name", return_value="mock_module_name") def test_app_name_derivation_main_module(mocker, mock_get_root_module_name): mocker.patch("cyclopts.core.sys.argv", ["__main__.py"]) app = App() assert app.name == ("mock_module_name",) mock_get_root_module_name.assert_called() def test_app_name_derivation_not_main_module(mocker): mocker.patch("cyclopts.core.sys.argv", ["my-script.py"]) app = App() assert app.name == ("my-script.py",) cyclopts-3.9.0/tests/test_app_utils.py000066400000000000000000000037271475451620500201600ustar00rootroot00000000000000import warnings import pytest import cyclopts.core from cyclopts import App from cyclopts.core import _log_framework_warning @pytest.fixture(autouse=True) def clear_cache(): # Setup _log_framework_warning.cache_clear() yield # Teardown _log_framework_warning.cache_clear() def test_app_iter(app): """Like a dictionary, __iter__ of an App should yield keys (command names).""" @app.command def foo(): pass @app.command def bar(): pass actual = list(app) assert actual == ["--help", "-h", "--version", "foo", "bar"] def test_app_iter_with_meta(app): @app.command def foo(): pass @app.command def bar(): pass @app.meta.command def fizz(): pass actual = list(app) assert actual == ["--help", "-h", "--version", "foo", "bar"] actual = list(app.meta) assert actual == ["--help", "-h", "--version", "fizz", "foo", "bar"] def test_app_update(): app1 = App() app2 = App() @app1.command def foo(): pass @app2.command def bar(): pass app1.update(app2) assert list(app1) == ["--help", "-h", "--version", "foo", "bar"] def test_log_framework_warning_unknown(): # Should not generate a warning for UNKNOWN framework with warnings.catch_warnings(): warnings.simplefilter("error") # Convert warnings to errors _log_framework_warning(cyclopts.core.TestFramework.UNKNOWN) # Should not raise def test_log_framework_warning_pytest(app): # Should generate a warning when called from non-cyclopts module with pytest.warns(UserWarning) as warning_records: try: app(exit_on_error=False) except Exception: pass assert len(warning_records) == 1 warning_msg = str(warning_records[0].message) assert ( warning_msg == 'Cyclopts application invoked without tokens under unit-test framework "pytest". Did you mean "app([])"?' ) cyclopts-3.9.0/tests/test_argument.py000066400000000000000000000414611475451620500177770ustar00rootroot00000000000000from collections import namedtuple from typing import Annotated, Dict, List, Optional, TypedDict, Union import pytest from cyclopts.argument import ( Argument, ArgumentCollection, _resolve_groups_from_callable, _resolve_parameter_name, is_typeddict, ) from cyclopts.group import Group from cyclopts.parameter import Parameter from cyclopts.token import Token Case = namedtuple("TestCase", ["args", "expected"]) def test_argument_collection_no_annotation_no_default(): def foo(a, b): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].hint is str assert collection[0].keys == () assert collection[0]._accepts_keywords is False assert collection[1].field_info.name == "b" assert collection[1].hint is str assert collection[1].keys == () assert collection[1]._accepts_keywords is False def test_argument_collection_no_annotation_default(): def foo(a="foo", b=100): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].hint is str assert collection[0].keys == () assert collection[0]._accepts_keywords is False assert collection[1].field_info.name == "b" assert collection[1].hint is int assert collection[1].keys == () assert collection[1]._accepts_keywords is False def test_argument_collection_basic_annotation(): def foo(a: str, b: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].hint is str assert collection[0].keys == () assert collection[0]._accepts_keywords is False assert collection[1].field_info.name == "b" assert collection[1].hint is int assert collection[1].keys == () assert collection[1]._accepts_keywords is False @pytest.mark.parametrize("type_", [dict, Dict]) def test_argument_collection_bare_dict(type_): def foo(a: type_, b: int): # pyright: ignore pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is type_ assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0]._accepts_arbitrary_keywords is True assert collection[1].field_info.name == "b" assert collection[1].parameter.name == ("--b",) assert collection[1].hint is int assert collection[1].keys == () assert collection[1]._accepts_keywords is False def test_argument_collection_typing_dict(): def foo(a: Dict[str, int], b: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].hint == Dict[str, int] assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0]._accepts_arbitrary_keywords is True assert collection[1].field_info.name == "b" assert collection[1].hint is int assert collection[1].keys == () assert collection[1]._accepts_keywords is False def test_argument_collection_typeddict(): class ExampleTypedDict(TypedDict): foo: str bar: int def foo(a: ExampleTypedDict, b: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 4 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is ExampleTypedDict assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0].children assert collection[1].field_info.name == "foo" assert collection[1].parameter.name == ("--a.foo",) assert collection[1].hint is str assert collection[1].keys == ("foo",) assert collection[1]._accepts_keywords is False assert not collection[1].children assert collection[2].field_info.name == "bar" assert collection[2].parameter.name == ("--a.bar",) assert collection[2].hint is int assert collection[2].keys == ("bar",) assert collection[2]._accepts_keywords is False assert not collection[2].children assert collection[3].field_info.name == "b" assert collection[3].parameter.name == ("--b",) assert collection[3].hint is int assert collection[3].keys == () assert collection[3]._accepts_keywords is False assert not collection[3].children def test_argument_collection_typeddict_nested(): class Inner(TypedDict): fizz: float buzz: Annotated[complex, Parameter(name="bazz")] class ExampleTypedDict(TypedDict): foo: Inner bar: int def foo(a: ExampleTypedDict, b: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 6 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is ExampleTypedDict assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0].children assert collection[1].field_info.name == "foo" assert collection[1].parameter.name == ("--a.foo",) assert collection[1].hint is Inner assert collection[1].keys == ("foo",) assert collection[1]._accepts_keywords is True assert collection[1].children assert collection[2].field_info.name == "fizz" assert collection[2].parameter.name == ("--a.foo.fizz",) assert collection[2].hint is float assert collection[2].keys == ("foo", "fizz") assert collection[2]._accepts_keywords is False assert not collection[2].children assert collection[3].field_info.name == "buzz" assert collection[3].parameter.name == ("--a.foo.bazz",) assert collection[3].hint is complex assert collection[3].keys == ("foo", "buzz") assert collection[3]._accepts_keywords is False assert not collection[3].children assert collection[4].field_info.name == "bar" assert collection[4].parameter.name == ("--a.bar",) assert collection[4].hint is int assert collection[4].keys == ("bar",) assert collection[4]._accepts_keywords is False assert not collection[4].children assert collection[5].field_info.name == "b" assert collection[5].parameter.name == ("--b",) assert collection[5].hint is int assert collection[5].keys == () assert collection[5]._accepts_keywords is False assert not collection[5].children def test_argument_collection_typeddict_annotated_keys_name_change(): class ExampleTypedDict(TypedDict): foo: Annotated[str, Parameter(name="fizz")] bar: Annotated[int, Parameter(name="buzz")] def foo(a: ExampleTypedDict, b: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 4 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is ExampleTypedDict assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0].children assert collection[1].field_info.name == "foo" assert collection[1].parameter.name == ("--a.fizz",) assert collection[1].hint is str assert collection[1].keys == ("foo",) assert collection[1]._accepts_keywords is False assert not collection[1].children assert collection[2].field_info.name == "bar" assert collection[2].parameter.name == ("--a.buzz",) assert collection[2].hint is int assert collection[2].keys == ("bar",) assert collection[2]._accepts_keywords is False assert not collection[2].children assert collection[3].field_info.name == "b" assert collection[3].parameter.name == ("--b",) assert collection[3].hint is int assert collection[3].keys == () assert collection[3]._accepts_keywords is False assert not collection[3].children def test_argument_collection_typeddict_annotated_keys_name_override(): class ExampleTypedDict(TypedDict): foo: Annotated[str, Parameter(name="--fizz")] bar: Annotated[int, Parameter(name="--buzz")] def foo(a: ExampleTypedDict, b: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 4 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is ExampleTypedDict assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0].children assert collection[1].field_info.name == "foo" assert collection[1].parameter.name == ("--fizz",) assert collection[1].hint is str assert collection[1].keys == ("foo",) assert collection[1]._accepts_keywords is False assert not collection[1].children assert collection[2].field_info.name == "bar" assert collection[2].parameter.name == ("--buzz",) assert collection[2].hint is int assert collection[2].keys == ("bar",) assert collection[2]._accepts_keywords is False assert not collection[2].children assert collection[3].field_info.name == "b" assert collection[3].parameter.name == ("--b",) assert collection[3].hint is int assert collection[3].keys == () assert collection[3]._accepts_keywords is False assert not collection[3].children def test_argument_collection_typeddict_flatten_root(): class ExampleTypedDict(TypedDict): foo: str bar: int def foo(a: Annotated[ExampleTypedDict, Parameter(name="*")], b: int): pass collection = ArgumentCollection._from_callable(foo) assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("*",) assert collection[0].hint is ExampleTypedDict assert collection[0].keys == () assert collection[0]._accepts_keywords is True assert collection[0].children assert collection[1].field_info.name == "foo" assert collection[1].parameter.name == ("--foo",) assert collection[1].hint is str assert collection[1].keys == ("foo",) assert collection[1]._accepts_keywords is False assert not collection[1].children assert collection[2].field_info.name == "bar" assert collection[2].parameter.name == ("--bar",) assert collection[2].hint is int assert collection[2].keys == ("bar",) assert collection[2]._accepts_keywords is False assert not collection[2].children assert collection[3].field_info.name == "b" assert collection[3].parameter.name == ("--b",) assert collection[3].hint is int assert collection[3].keys == () assert collection[3]._accepts_keywords is False assert not collection[3].children def test_argument_collection_var_positional(): def foo(a: int, *b: float): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].parameter.name == ("--a",) assert collection[0].hint is int assert collection[0].keys == () assert collection[0]._accepts_keywords is False assert collection[1].field_info.name == "b" assert collection[1].parameter.name == ("B",) assert collection[1].hint == tuple[float, ...] assert collection[1].keys == () assert collection[1]._accepts_keywords is False def test_argument_collection_var_keyword(): def foo(a: int, **b: float): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is int assert collection[0].keys == () assert collection[0]._accepts_keywords is False assert collection[1].field_info.name == "b" assert collection[1].parameter.name == ("--[KEYWORD]",) assert collection[1].hint == dict[str, float] assert collection[1].keys == () assert collection[1]._accepts_keywords is True def test_argument_collection_var_keyword_named(): def foo(a: int, **b: Annotated[float, Parameter(name=("--foo", "--bar"))]): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 2 assert collection[0].field_info.name == "a" assert collection[0].parameter.name == ("--a",) assert collection[0].hint is int assert collection[0].keys == () assert collection[0]._accepts_keywords is False assert collection[1].field_info.name == "b" assert collection[1].parameter.name == ("--foo", "--bar") assert collection[1].hint == dict[str, float] assert collection[1].keys == () assert collection[1]._accepts_keywords is True def test_argument_collection_var_keyword_match(): def foo(a: int, **b: float): pass collection = ArgumentCollection._from_callable(foo) argument, keys, _ = collection.match("--fizz") assert keys == ("fizz",) assert argument.field_info.name == "b" @pytest.mark.parametrize( "args, expected", [ Case(args=(), expected=()), Case(args=(("foo",),), expected=("--foo",)), Case(args=(("--foo",),), expected=("--foo",)), Case(args=(("--foo", "--bar"),), expected=("--foo", "--bar")), Case(args=(("--foo",), ("--bar",)), expected=("--bar",)), Case(args=(("--foo",), ("baz",)), expected=("--foo.baz",)), Case(args=(("--foo",), ("--bar", "baz")), expected=("--bar", "--foo.baz")), Case(args=(("--foo", "--bar"), ("baz",)), expected=("--foo.baz", "--bar.baz")), Case(args=(("*",), ("bar",)), expected=("--bar",)), Case(args=(("--foo", "*"), ("bar",)), expected=("--foo.bar", "--bar")), Case(args=(("--foo",), ("*",), ("bar",)), expected=("--foo.bar",)), Case(args=(("foo",), ("--bar",)), expected=("--bar",)), Case(args=(("foo",), ("bar",)), expected=("--foo.bar",)), ], ) def test_resolve_parameter_name(args, expected): assert _resolve_parameter_name(*args) == expected def test_resolve_groups_from_callable(): class User(TypedDict): name: Annotated[str, Parameter(group="Inside Typed Dict")] age: Annotated[int, Parameter(group="Inside Typed Dict")] height: float def build( config1: str, config2: Annotated[str, Parameter()], flag1: Annotated[bool, Parameter(group="Flags")] = False, flag2: Annotated[bool, Parameter(group=("Flags", "Other Flags"))] = False, user: Optional[User] = None, ): pass actual = _resolve_groups_from_callable(build) assert actual == [Group("Parameters"), Group("Flags"), Group("Other Flags"), Group("Inside Typed Dict")] def test_argument_convert(): argument = Argument( hint=List[int], tokens=[ Token(value="42", source="test"), Token(value="70", source="test"), ], ) assert argument.convert() == [42, 70] def test_argument_convert_dict(): def foo(bar: Dict[str, int]): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 1 argument = collection[0] # Sanity check the match method assert argument.match("--bar.buzz") == (("buzz",), None) argument.append(Token(value="7", source="test", keys=("fizz",))) argument.append(Token(value="12", source="test", keys=("buzz",))) assert argument.convert() == {"fizz": 7, "buzz": 12} def test_argument_convert_var_keyword(): def foo(**kwargs: int): pass collection = ArgumentCollection._from_callable(foo) assert len(collection) == 1 argument = collection[0] # Sanity check the match method assert argument.match("--fizz") == (("fizz",), None) argument.append(Token(value="7", source="test", keys=("fizz",))) argument.append(Token(value="12", source="test", keys=("buzz",))) assert argument.convert() == {"fizz": 7, "buzz": 12} def test_argument_convert_cparam_provided(): def my_converter(type_, tokens): return f"my_converter_{tokens[0].value}" argument = Argument( hint=str, tokens=[Token(value="my_value", source="test")], parameter=Parameter( converter=my_converter, ), ) assert argument.convert() == "my_converter_my_value" class ExampleTypedDict(TypedDict): foo: str bar: int @pytest.mark.parametrize( "hint", [ ExampleTypedDict, Optional[ExampleTypedDict], Annotated[ExampleTypedDict, "foo"], # A union including a Typed Dict is allowed. Union[ExampleTypedDict, str, int], ], ) def test_is_typed_dict_true(hint): assert is_typeddict(hint) @pytest.mark.parametrize( "hint", [ list, dict, Dict, Dict[str, int], ], ) def test_is_typed_dict_false(hint): assert not is_typeddict(hint) cyclopts-3.9.0/tests/test_async.py000066400000000000000000000012521475451620500172640ustar00rootroot00000000000000from cyclopts import App def test_async_handler(): app = App() @app.command(name="command") async def async_handler(): return "Async handler works" assert app("command") == "Async handler works" def test_async_handler_with_subcommand_works(): app = App() sub_app = App(name="foo") app.command(sub_app) @sub_app.command(name="bar") async def async_handler(): return "Async handler works" assert app("foo bar") == "Async handler works" def test_handler(): app = App() @app.command(name="command") def sync_handler(): return "Sync handler works" assert app("command") == "Sync handler works" cyclopts-3.9.0/tests/test_bind_attrs.py000066400000000000000000000151401475451620500203010ustar00rootroot00000000000000from textwrap import dedent from typing import Annotated, Dict, Optional import pytest from attrs import define, field from cyclopts import Parameter from cyclopts.exceptions import MissingArgumentError, UnknownOptionError @define class Outfit: body: str head: str @define class User: id: int name: str = "John Doe" tastes: Dict[str, int] = field(factory=dict) outfit: Optional[Outfit] = None admin: Annotated[bool, Parameter(negative="not-admin")] = False vip: Annotated[bool, Parameter(negative="--not-vip")] = False staff: Annotated[bool, Parameter(parse=False)] = False def test_bind_attrs(app, assert_parse_args, console): @app.command def foo(user: User): pass assert_parse_args( foo, "foo --user.id=123 --user.tastes.wine=9 --user.tastes.cheese=7 --user.tastes.cabbage=1 --user.outfit.body=t-shirt --user.outfit.head=baseball-cap --user.admin", User( id=123, tastes={"wine": 9, "cheese": 7, "cabbage": 1}, outfit=Outfit(body="t-shirt", head="baseball-cap"), admin=True, ), ) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_attrs foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * USER.ID --user.id [required] │ │ USER.NAME --user.name [default: John Doe] │ │ --user.tastes │ │ --user.outfit.body │ │ --user.outfit.head │ │ --user.admin [default: False] │ │ --user.not-admin │ │ --user.vip --not-vip [default: False] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_attrs_flatten(app, assert_parse_args, console): @app.command def foo(user: Annotated[User, Parameter(name="*")]): pass assert_parse_args( foo, "foo --id=123 --tastes.wine=9 --tastes.cheese=7 --tastes.cabbage=1 --outfit.body=t-shirt --outfit.head=baseball-cap --admin", User( id=123, tastes={"wine": 9, "cheese": 7, "cabbage": 1}, outfit=Outfit(body="t-shirt", head="baseball-cap"), admin=True, ), ) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_attrs foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * ID --id [required] │ │ NAME --name [default: John Doe] │ │ --tastes │ │ --outfit.body │ │ --outfit.head │ │ --admin --not-admin [default: False] │ │ --vip --not-vip [default: False] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_attrs_accepts_keys_false(app, assert_parse_args, console): @define class SimpleClass: value: int name: str @app.command def foo(example: Annotated[SimpleClass, Parameter(accepts_keys=False)]): pass assert_parse_args(foo, "foo 5 foo", SimpleClass(5, "foo")) assert_parse_args(foo, "foo --example=5 foo", SimpleClass(5, "foo")) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_attrs foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * EXAMPLE --example [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_attrs_kw_only(app, assert_parse_args): @define class Engine: cylinders: int volume: float power: Annotated[float, Parameter(name="--power")] = field(kw_only=True) @app.default def default(engine: Engine): pass assert_parse_args(default, "4 100 --power=200", Engine(4, 100, power=200)) assert_parse_args(default, "--power=200 4 100", Engine(4, 100, power=200)) assert_parse_args(default, "4 --power=200 100", Engine(4, 100, power=200)) with pytest.raises(MissingArgumentError): app.parse_args("4 100 200", exit_on_error=False) def test_bind_attrs_unknown_option(app, assert_parse_args): @define class Engine: cylinders: int volume: float @app.default def default(engine: Engine): pass with pytest.raises(UnknownOptionError): app("--engine.cylinders 4 --this-parameter-does-not-exist 100", exit_on_error=False) def test_bind_attrs_alias(app, assert_parse_args): @define class Engine: cylinders: int volume: float = field(alias="cc") @app.default def default(engine: Engine): pass assert_parse_args(default, "--engine.cylinders 4 --engine.cc 100", Engine(cylinders=4, cc=100.0)) with pytest.raises(UnknownOptionError): app("--engine.cylinders 4 --engine.volume 100", exit_on_error=False) cyclopts-3.9.0/tests/test_bind_basic.py000066400000000000000000000275551475451620500202420ustar00rootroot00000000000000import sys from typing import Annotated, Any, Optional import pytest from cyclopts import Parameter from cyclopts.exceptions import ( ArgumentOrderError, CoercionError, InvalidCommandError, MissingArgumentError, RepeatArgumentError, UnusedCliTokensError, ) from cyclopts.group import Group def test_parse_known_args(app): @app.command def foo(a: int, b: int): pass command, _, unused_tokens, ignored = app.parse_known_args("foo 1 2 --bar 100") assert ignored == {} assert command == foo assert unused_tokens == ["--bar", "100"] @pytest.mark.parametrize( "cmd_str", [ "foo 1 2 3", "foo 1 2 --c=3", "foo --a 1 --b 2 --c 3", "foo --c 3 --b=2 --a 1", ], ) def test_basic_1(app, cmd_str, assert_parse_args): @app.command def foo(a: int, b: int, c: int): pass assert_parse_args(foo, cmd_str, 1, 2, 3) @pytest.mark.parametrize( "cmd_str", [ "foo 1 2 3 --d 10 --some-flag", "foo --some-flag 1 --b=2 --c 3 --d 10", "foo 1 2 --some-flag 3 --d 10", ], ) def test_basic_2(app, cmd_str, assert_parse_args): @app.command def foo(a: int, b: int, c: int, d: int = 5, *, some_flag: bool = False): pass assert_parse_args(foo, cmd_str, 1, 2, 3, d=10, some_flag=True) def test_basic_allow_hyphen_or_underscore(app, assert_parse_args): @app.default def default(foo_bar): pass assert_parse_args(default, "--foo-bar=bazz", "bazz") assert_parse_args(default, "--foo_bar=bazz", "bazz") def test_out_of_order_mixed_positional_or_keyword(app, assert_parse_args): @app.command def foo(a, b, c): pass with pytest.raises(ArgumentOrderError): app.parse_args("foo --b=5 1 2", print_error=False, exit_on_error=False) def test_command_rename(app, assert_parse_args): @app.command(name="bar") def foo(): pass assert_parse_args(foo, "bar") def test_command_delete(app, assert_parse_args): @app.command def foo(): pass del app["foo"] with pytest.raises(InvalidCommandError): assert_parse_args(foo, "foo") def test_command_multiple_alias(app, assert_parse_args): @app.command(name=["bar", "baz"]) def foo(): pass assert_parse_args(foo, "bar") assert_parse_args(foo, "baz") @pytest.mark.parametrize( "cmd_str", [ "foo --age 10", "foo --duration 10", "foo -a 10", ], ) def test_multiple_names(app, cmd_str, assert_parse_args): @app.command def foo(age: Annotated[int, Parameter(name=["--age", "--duration", "-a"])]): pass assert_parse_args(foo, cmd_str, age=10) @pytest.mark.parametrize( "cmd_str", [ "--job-name foo", "-j foo", ], ) def test_short_name_j(app, cmd_str, assert_parse_args): """ "-j" previously didn't work as a short-name because it's a valid complex value. https://github.com/BrianPugh/cyclopts/issues/328 """ @app.default def main( *, job_name: Annotated[str, Parameter(name=["--job-name", "-j"], negative=False)], ): pass assert_parse_args(main, cmd_str, job_name="foo") @pytest.mark.parametrize( "cmd_str", [ "foo --age 10", "foo --duration 10", "foo -a 10", ], ) def test_multiple_names_no_hyphen(app, cmd_str, assert_parse_args): @app.command def foo(age: Annotated[int, Parameter(name=["age", "duration", "-a"])]): pass assert_parse_args(foo, cmd_str, age=10) @pytest.mark.parametrize( "cmd_str", [ "foo 1", "foo --a=1", "foo --a 1", ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_optional_nonrequired_implicit_coercion(app, cmd_str, annotated, assert_parse_args): """ For a union without an explicit coercion, the first non-None type annotation should be used. In this case, it's ``int``. """ if annotated: @app.command def foo(a: Annotated[Optional[int], Parameter(help="help for a")] = None): pass else: @app.command def foo(a: Optional[int] = None): pass assert_parse_args(foo, cmd_str, 1) @pytest.mark.skipif(sys.version_info < (3, 10), reason="Pipe Typing Syntax") @pytest.mark.parametrize( "cmd_str", [ "foo 1", "foo --a=1", "foo --a 1", ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_optional_nonrequired_implicit_coercion_python310_syntax(app, cmd_str, annotated, assert_parse_args): """ For a union without an explicit coercion, the first non-None type annotation should be used. In this case, it's ``int``. """ if annotated: @app.command def foo(a: Annotated[int | None, Parameter(help="help for a")] = None): # pyright: ignore pass else: @app.command def foo(a: int | None = None): # pyright: ignore pass assert_parse_args(foo, cmd_str, 1) @pytest.mark.parametrize( "cmd_str", [ "--foo val1 --foo val2", ], ) def test_exception_repeat_argument(app, cmd_str): @app.default def default(foo: str): pass with pytest.raises(RepeatArgumentError): app.parse_args(cmd_str, print_error=False, exit_on_error=False) @pytest.mark.parametrize( "cmd_str", [ "--foo val1 --foo val2", ], ) def test_exception_repeat_argument_kwargs(app, cmd_str): @app.default def default(**kwargs: str): pass with pytest.raises(RepeatArgumentError): app.parse_args(cmd_str, print_error=False, exit_on_error=False) def test_exception_unused_token(app): @app.default def default(foo: str): pass with pytest.raises(UnusedCliTokensError): app.parse_args("foo bar", print_error=False, exit_on_error=False) @pytest.mark.parametrize( "cmd_str", [ "foo 1", "foo --a=1", "foo --a 1", ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_bind_no_hint_no_default(app, cmd_str, annotated, assert_parse_args): """Parameter with no type hint & no default should be treated as a ``str``.""" if annotated: @app.command def foo(a: Annotated[Any, Parameter(help="help for a")]): # pyright: ignore[reportRedeclaration] pass else: @app.command def foo(a): # pyright: ignore[reportRedeclaration] pass assert_parse_args(foo, cmd_str, "1") @pytest.mark.parametrize( "cmd_str", [ "foo 1", "foo --a=1", "foo --a 1", ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_bind_no_hint_none_default(app, cmd_str, annotated, assert_parse_args): """Parameter with no type hint & ``None`` default should be treated as a ``str``.""" if annotated: @app.command def foo(a: Annotated[Any, Parameter(help="help for a")] = None): # pyright: ignore[reportRedeclaration] pass else: @app.command def foo(a=None): # pyright: ignore[reportRedeclaration] pass assert_parse_args(foo, cmd_str, "1") @pytest.mark.parametrize( "cmd_str", [ "foo 1", "foo --a=1", "foo --a 1", ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_bind_no_hint_typed_default(app, cmd_str, annotated, assert_parse_args): """Parameter with no type hint & typed default should be treated as a ``type(default)``.""" if annotated: @app.command def foo(a: Annotated[Any, Parameter(help="help for a")] = 5): # pyright: ignore[reportRedeclaration] pass else: @app.command def foo(a=5): # pyright: ignore[reportRedeclaration] pass assert_parse_args(foo, cmd_str, 1) @pytest.mark.parametrize( "cmd_str", [ "foo 1", "foo --a=1", "foo --a 1", ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_bind_any_hint(app, cmd_str, annotated, assert_parse_args): """The ``Any`` type hint should be treated as a ``str``.""" if annotated: @app.command def foo(a: Annotated[Any, Parameter(help="help for a")] = None): pass else: @app.command def foo(a: Any = None): pass assert_parse_args(foo, cmd_str, "1") @pytest.mark.parametrize( "cmd_str", [ "1", "0b1", "0x01", "1.0", "0.9", ], ) def test_bind_int_advanced(app, cmd_str, assert_parse_args): @app.default def foo(a: int): pass assert_parse_args(foo, cmd_str, 1) def test_bind_int_advanced_coercion_error(app): @app.default def foo(a: int): pass with pytest.raises(CoercionError): app.parse_args("foo", exit_on_error=False) def test_bind_override_app_groups(app): g_commands = Group("Custom Commands") g_arguments = Group("Custom Arguments") g_parameters = Group("Custom Parameters") @app.command(group_commands=g_commands, group_arguments=g_arguments, group_parameters=g_parameters) def foo(): pass assert app["foo"].group_commands == g_commands assert app["foo"].group_arguments == g_arguments assert app["foo"].group_parameters == g_parameters def test_bind_version(app, capsys): app.version = "1.2.3" actual_command, actual_bind, ignored = app.parse_args("--version") assert ignored == {} assert actual_command == app.version_print actual_command(*actual_bind.args, **actual_bind.kwargs) captured = capsys.readouterr() assert captured.out == "1.2.3\n" def test_bind_version_factory(app, capsys): app.version = lambda: "1.2.3" actual_command, actual_bind, ignored = app.parse_args("--version") assert ignored == {} assert actual_command == app.version_print actual_command(*actual_bind.args, **actual_bind.kwargs) captured = capsys.readouterr() assert captured.out == "1.2.3\n" @pytest.mark.parametrize( "cmd_str_e", [ ("foo 1 2 3", MissingArgumentError), ("foo 1 2", MissingArgumentError), ], ) def test_missing_keyword_argument(app, cmd_str_e): cmd_str, e = cmd_str_e @app.command def foo(a: int, b: int, c: int, *, d: int): pass with pytest.raises(e): app.parse_args(cmd_str, print_error=False, exit_on_error=False) @pytest.mark.parametrize( "cmd_str", [ "1 -- --2 3 4", "-- 1 --2 3 4", "--c=3 4 -- 1 --2", "--c 3 4 -- 1 --2", ], ) def test_default_double_hyphen_end_of_options_delimiter(app, cmd_str, assert_parse_args): @app.default def foo(a: int, b: str, c: tuple[int, int]): pass assert_parse_args(foo, cmd_str, 1, "--2", (3, 4)) def test_disabled_double_hyphen_end_of_options_delimiter_from_app(app, assert_parse_args): app.end_of_options_delimiter = "" @app.default def foo(a: int, b: Annotated[str, Parameter(allow_leading_hyphen=True)], c: tuple[int, int]): pass assert_parse_args(foo, "1 -- 3 4", 1, "--", (3, 4)) def test_disabled_double_hyphen_end_of_options_delimiter_from_parse_args(app, assert_parse_args_config): @app.default def foo(a: int, b: Annotated[str, Parameter(allow_leading_hyphen=True)], c: tuple[int, int]): pass assert_parse_args_config({"end_of_options_delimiter": ""}, foo, "1 -- 3 4", 1, "--", (3, 4)) def test_end_of_options_delimiter_from_parse_args(app, assert_parse_args): app.end_of_options_delimiter = "AND" @app.default def foo(a: int, b: str, c: tuple[int, int]): pass assert_parse_args(foo, "1 AND --2 3 4", 1, "--2", (3, 4)) def test_end_of_options_delimiter_override(app, assert_parse_args_config): app.end_of_options_delimiter = "AND" # This gets overridden @app.default def foo(a: int, b: str, c: tuple[int, int]): pass assert_parse_args_config({"end_of_options_delimiter": "DELIMIT"}, foo, "1 DELIMIT --2 3 4", 1, "--2", (3, 4)) cyclopts-3.9.0/tests/test_bind_boolean_flag.py000066400000000000000000000101361475451620500215540ustar00rootroot00000000000000from typing import Annotated import pytest from cyclopts import ( Group, Parameter, UnknownOptionError, ) from cyclopts.exceptions import CoercionError @pytest.mark.parametrize( "cmd_str,expected", [ ("--my-flag", True), ("--my-flag=true", True), ("--my-flag=false", False), ("--no-my-flag", False), ], ) def test_boolean_flag_default(app, cmd_str, expected, assert_parse_args): @app.default def foo(my_flag: bool = True): pass assert_parse_args(foo, cmd_str, expected) def test_boolean_flag_app_parameter_default(app, assert_parse_args): app.default_parameter = Parameter(negative="") @app.default def foo(my_flag: bool = True): pass # Normal positive flag should still work. assert_parse_args(foo, "--my-flag", True) with pytest.raises(UnknownOptionError) as e: app.parse_args("--no-my-flag", exit_on_error=False) assert str(e.value) == 'Unknown option: "--no-my-flag". Did you mean "--my-flag"?' def test_boolean_flag_app_parameter_negative(app, assert_parse_args): @app.default def foo(my_flag: Annotated[bool, Parameter("", negative="--no-my-flag")] = True): pass assert_parse_args(foo, "--no-my-flag", False) with pytest.raises(UnknownOptionError): app.parse_args("--my-flag", exit_on_error=False, print_error=True) assert_parse_args(foo, "--no-my-flag=True", False) assert_parse_args(foo, "--no-my-flag=False", True) with pytest.raises(CoercionError): app("--no-my-flag=", exit_on_error=False) def test_boolean_flag_app_parameter_default_annotated_override(app, assert_parse_args): app.default_parameter = Parameter(negative="") @app.default def foo(my_flag: Annotated[bool, Parameter(negative="--NO-flag")] = True): pass assert_parse_args(foo, "--my-flag", True) assert_parse_args(foo, "--NO-flag", False) def test_boolean_flag_app_parameter_default_nested_annotated_override(app, assert_parse_args): app.default_parameter = Parameter(negative="") def my_converter(type_, tokens): return 5 my_int = Annotated[int, Parameter(converter=my_converter)] @app.default def foo(*, foo: Annotated[my_int, Parameter(name="--bar")] = True): # pyright: ignore[reportInvalidTypeForm] pass assert_parse_args(foo, "--bar=10", foo=5) def test_boolean_flag_group_default_parameter_resolution_1(app, assert_parse_args): food_group = Group("Food", default_parameter=Parameter(negative_bool="group-")) @app.default def foo(flag: Annotated[bool, Parameter(group=food_group)]): pass assert_parse_args(foo, "--group-flag", False) @pytest.mark.parametrize( "cmd_str,expected", [ ("--bar", True), ("--no-bar", False), ], ) def test_boolean_flag_custom_positive(app, cmd_str, expected, assert_parse_args): @app.default def foo(my_flag: Annotated[bool, Parameter(name="--bar")] = True): pass assert_parse_args(foo, cmd_str, expected) @pytest.mark.parametrize( "cmd_str,expected", [ ("--bar", True), ("--no-bar", False), ], ) def test_boolean_flag_custom_short_positive(app, cmd_str, expected, assert_parse_args): @app.default def foo(my_flag: Annotated[bool, Parameter(name=["--bar", "-b"])] = True): pass assert_parse_args(foo, cmd_str, expected) @pytest.mark.parametrize( "cmd_str,expected", [ ("--my-flag", True), ("--yesnt-my-flag", False), ], ) def test_boolean_flag_custom_negative(app, cmd_str, expected, assert_parse_args): @app.default def foo(my_flag: Annotated[bool, Parameter(negative="--yesnt-my-flag")] = True): pass assert_parse_args(foo, cmd_str, expected) @pytest.mark.parametrize( "negative", ["", (), []], ) def test_boolean_flag_disable_negative(app, negative, assert_parse_args): @app.default def foo(my_flag: Annotated[bool, Parameter(negative=negative)] = True): pass assert_parse_args(foo, "--my-flag", True) with pytest.raises(UnknownOptionError): assert_parse_args(foo, "--no-my-flag", True) cyclopts-3.9.0/tests/test_bind_converter_validator.py000066400000000000000000000121501475451620500232160ustar00rootroot00000000000000from typing import Annotated from unittest.mock import Mock import pytest from cyclopts import Parameter, ValidationError from cyclopts.exceptions import CoercionError @pytest.fixture def validator(): return Mock() def test_custom_converter(app, assert_parse_args): def custom_converter(type_, tokens): return 2 * int(tokens[0].value) @app.default def foo(age: Annotated[int, Parameter(converter=custom_converter)]): pass assert_parse_args(foo, "5", age=10) def test_custom_converter_dict(app, assert_parse_args): def custom_converter(type_, tokens): return {k: 2 * int(v[0].value) for k, v in tokens.items()} @app.default def foo(*, color: Annotated[dict[str, int], Parameter(converter=custom_converter)]): pass assert_parse_args(foo, "--color.red 5 --color.green 10", color={"red": 10, "green": 20}) def test_custom_converter_user_value_error_single_token(app): def custom_converter(type_, tokens): raise ValueError @app.default def foo(age: Annotated[int, Parameter(converter=custom_converter)]): pass with pytest.raises(CoercionError) as e: app("5", exit_on_error=False) assert str(e.value) == 'Invalid value for "AGE": unable to convert "5" into int.' def test_custom_converter_user_value_error_multi_token(app): def custom_converter(type_, tokens): raise ValueError @app.default def foo(age: Annotated[tuple[int, int], Parameter(converter=custom_converter)]): pass with pytest.raises(CoercionError) as e: app("5 6", exit_on_error=False) assert str(e.value) == 'Invalid value for "--age": unable to convert value to tuple[int, int].' def test_custom_converter_user_value_error_with_message(app): def custom_converter(type_, tokens): raise ValueError("Some user-provided message.") @app.default def foo(age: Annotated[int, Parameter(converter=custom_converter)]): pass with pytest.raises(CoercionError) as e: app("5", exit_on_error=False) assert str(e.value) == "Some user-provided message." def test_custom_converter_user_kwargs_error(app): def custom_converter(type_, tokens): raise ValueError @app.default def foo(**kwargs: Annotated[int, Parameter(converter=custom_converter)]): pass with pytest.raises(CoercionError) as e: app("--foo 5", exit_on_error=False) assert str(e.value) == 'Invalid value for "--foo": unable to convert "5" into int.' def test_custom_converter_user_kwargs_error_with_message(app): def custom_converter(type_, tokens): raise ValueError("Some user-provided message.") @app.default def foo(**kwargs: Annotated[int, Parameter(converter=custom_converter)]): pass with pytest.raises(CoercionError) as e: app("--foo 5", exit_on_error=False) assert str(e.value) == "Invalid value for --foo: Some user-provided message." def test_custom_validator_positional_or_keyword(app, assert_parse_args, validator): @app.default def foo(age: Annotated[int, Parameter(validator=validator)]): pass assert_parse_args(foo, "10", age=10) validator.assert_called_once_with(int, 10) def test_custom_validator_var_keyword(app, assert_parse_args, validator): @app.default def foo(**age: Annotated[int, Parameter(validator=validator)]): pass assert_parse_args(foo, "--age=10", age=10) validator.assert_called_once_with(int, 10) def test_custom_validator_var_positional(app, assert_parse_args, validator): @app.default def foo(*age: Annotated[int, Parameter(validator=validator)]): pass assert_parse_args(foo, "10", 10) validator.assert_called_once_with(int, 10) def test_custom_validators(app, assert_parse_args): def lower_bound(type_, value): if value <= 0: raise ValueError("An unreasonable age was entered.") def upper_bound(type_, value): if value > 150: raise ValueError("An unreasonable age was entered.") @app.default def foo(age: Annotated[int, Parameter(validator=[lower_bound, upper_bound])]): pass assert_parse_args(foo, "10", 10) with pytest.raises(ValidationError): app.parse_args("0", print_error=False, exit_on_error=False) with pytest.raises(ValidationError): app.parse_args("200", print_error=False, exit_on_error=False) def test_custom_converter_and_validator(app, assert_parse_args, validator): def custom_validator(type_, value): if not (0 < value < 150): raise ValueError("An unreasonable age was entered.") def custom_converter(type_, tokens): return 2 * int(tokens[0].value) @app.default def foo(age: Annotated[int, Parameter(converter=custom_converter, validator=validator)]): pass assert_parse_args(foo, "5", 10) validator.assert_called_once_with(int, 10) def test_custom_command_validator(app, assert_parse_args): validator = Mock() @app.default(validator=validator) def foo(a: int, b: int, c: int): pass assert_parse_args(foo, "1 2 3", 1, 2, 3) validator.assert_called_once_with(a=1, b=2, c=3) cyclopts-3.9.0/tests/test_bind_custom_type.py000066400000000000000000000013301475451620500215130ustar00rootroot00000000000000from typing import Annotated from cyclopts import Parameter class OneToken: def __init__(self, value: int): self.value = value def __eq__(self, other): return self.value == other.value def test_custom_type_one_token_implicit_convert(app): @app.default def default(value: OneToken): return value res = app("5") assert res == OneToken(5) def test_custom_type_one_token_explicit_convert(app): def converter(type_, tokens): assert len(tokens) == 1 return type_(int(tokens[0].value) + 10) @app.default def default(value: Annotated[OneToken, Parameter(converter=converter)]): return value res = app("5") assert res == OneToken(15) cyclopts-3.9.0/tests/test_bind_dataclasses.py000066400000000000000000000310231475451620500214310ustar00rootroot00000000000000import sys from dataclasses import dataclass, field from textwrap import dedent from typing import Annotated, Dict import pytest from cyclopts import Parameter from cyclopts.exceptions import ( ArgumentOrderError, MissingArgumentError, UnusedCliTokensError, ) @dataclass class User: id: int name: str = "John Doe" tastes: Dict[str, int] = field(default_factory=dict) def test_bind_dataclass(app, assert_parse_args, console): @app.command def foo(some_number: int, user: User): pass external_data = { "id": 123, # "name" is purposely missing. "tastes": { "wine": 9, "cheese": 7, "cabbage": 1, }, } assert_parse_args( foo, "foo 100 --user.id=123 --user.tastes.wine=9 --user.tastes.cheese=7 --user.tastes.cabbage=1", 100, User(**external_data), ) def test_bind_dataclass_missing_all_arguments(app, assert_parse_args, console): """We expect to see the first subargument (--user.id) in the error message, not the root "--user". """ @app.default def default(some_number: int, user: User): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app("123", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Parameter "--user.id" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.mark.skipif(sys.version_info < (3, 10), reason="field(kw_only=True) doesn't exist.") def test_bind_dataclass_recursive(app, assert_parse_args, console): @dataclass class Wheel: diameter: int "Diameter of wheel in inches." @dataclass class Engine: cylinders: int "Number of cylinders the engine has." hp: Annotated[float, Parameter(name=("horsepower", "p"))] "Amount of horsepower the engine can generate." diesel: bool = False "If this engine consumes diesel, instead of gasoline." @dataclass class Car: name: str "The name/model of the car." mileage: float "How many miles the car has driven." engine: Annotated[Engine, Parameter(name="*", group="Engine")] = field(kw_only=True) # pyright: ignore "The kind of engine the car is using." wheel: Wheel "The kind of wheels the car is using." n_axles: int = 2 "Number of axles the car has." @app.command def build(*, license_plate: str, car: Car): """Build a car. Parameters ---------- license_plate: str License plate identifier to give to car. car: Car Car specifications. """ assert_parse_args( build, "build --car.name=ford --car.mileage=500 --car.cylinders=4 --car.p=200 --car.wheel.diameter=18 --license-plate=ABCDEFG", car=Car( name="ford", mileage=500, engine=Engine(cylinders=4, hp=200), wheel=Wheel(diameter=18), ), license_plate="ABCDEFG", ) with console.capture() as capture: app("build --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_dataclasses build [OPTIONS] Build a car. ╭─ Engine ───────────────────────────────────────────────────────────╮ │ * --car.cylinders Number of cylinders the engine has. │ │ [required] │ │ * --car.horsepower --car.p Amount of horsepower the engine can │ │ generate. [required] │ │ --car.diesel If this engine consumes diesel, │ │ --car.no-diesel instead of gasoline. [default: False] │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * --license-plate License plate identifier to give to car. │ │ [required] │ │ * --car.name The name/model of the car. [required] │ │ * --car.mileage How many miles the car has driven. │ │ [required] │ │ * --car.wheel.diameter Diameter of wheel in inches. [required] │ │ --car.n-axles Number of axles the car has. [default: 2] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_dataclass_recursive_missing_arg(app, assert_parse_args, console): """The ``engine`` parameter itself is optional, but if specified it has 2 required fields.""" @dataclass class Engine: cylinders: int hp: float = 100 @dataclass class Car: name: str mileage: float engine: Annotated[Engine, Parameter(name="*", group="Engine")] = field(default_factory=lambda: Engine(8, 500)) @app.command def build(*, license_plate: str, car: Car): pass # Specifying a complete engine works. assert_parse_args( build, "build --car.name=ford --car.mileage=500 --car.cylinders=4 --car.hp=200 --license-plate=ABCDEFG", car=Car(name="ford", mileage=500, engine=Engine(cylinders=4, hp=200)), license_plate="ABCDEFG", ) # Specifying NO engine works. assert_parse_args( build, "build --car.name=ford --car.mileage=500 --license-plate=ABCDEFG", car=Car(name="ford", mileage=500), license_plate="ABCDEFG", ) # Partially defining an engine does NOT work. with console.capture() as capture, pytest.raises(MissingArgumentError): app.parse_args( "build --car.name=ford --car.mileage=500 --car.hp=200 --license-plate=ABCDEFG", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "build" parameter "--car.cylinders" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.mark.parametrize( "cmd", [ "'Bob Smith' 30", "--nickname='Bob Smith' --player.years-young=30", ], ) def test_bind_dataclass_double_name_override_no_hyphen(app, assert_parse_args, console, cmd): @dataclass class User: # Beginning with "--" will completely override the parenting parameter name. name: Annotated[str, Parameter(name="--nickname")] # Not beginning with "--" will tack it on to the parenting parameter name. age: Annotated[int, Parameter(name="years-young")] @app.default def main(user: Annotated[User, Parameter(name="player")]): # but what about without --? print(user) assert_parse_args(main, cmd, user=User("Bob Smith", 30)) @pytest.mark.parametrize( "cmd_str", [ "100 200", "--a 100 --bar 200", "--bar 200 100", ], ) def test_bind_dataclass_positionally(app, assert_parse_args, cmd_str, console): @dataclass class Config: a: int = 1 """Docstring for a.""" b: Annotated[int, Parameter(name="bar")] = 2 """This is the docstring for python parameter "b".""" @app.default def my_default_command(config: Annotated[Config, Parameter(name="*")]): print(f"{config=}") assert_parse_args(my_default_command, cmd_str, Config(a=100, b=200)) with console.capture() as capture: app("build --help", console=console) actual = capture.get() expected = dedent( """\ Usage: my-default-command COMMAND [ARGS] [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ A --a Docstring for a. [default: 1] │ │ BAR --bar This is the docstring for python parameter "b". │ │ [default: 2] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.mark.skipif(sys.version_info < (3, 10), reason="field(kw_only=True) doesn't exist.") def test_bind_dataclass_positionally_with_keyword_only_exception_no_default(app, assert_parse_args): @dataclass class Config: a: int = 1 """Docstring for a.""" b: Annotated[int, Parameter(name="bar")] = 2 """This is the docstring for python parameter "b".""" c: int = field(kw_only=True) # pyright: ignore @app.default def my_default_command(foo, config: Annotated[Config, Parameter(name="*")], bar): print(f"{config=}") expected = ("v1", Config(100, 200, c=300), "v2") assert_parse_args(my_default_command, "v1 100 200 v2 --c=300", *expected) assert_parse_args(my_default_command, "--c=300 v1 100 200 v2", *expected) with pytest.raises(MissingArgumentError): app.parse_args("v1 100 200 300 v2", exit_on_error=False) with pytest.raises(ArgumentOrderError): app.parse_args("v1 --a=100 200 300 v2", exit_on_error=False) with pytest.raises(ArgumentOrderError): app.parse_args("v1 --bar=v2 100 200 --c=300", exit_on_error=False) @pytest.mark.skipif(sys.version_info < (3, 10), reason="field(kw_only=True) doesn't exist.") def test_bind_dataclass_positionally_with_keyword_only_exception_with_default(app, assert_parse_args): @dataclass class Config: a: int = 1 """Docstring for a.""" b: Annotated[int, Parameter(name="bar")] = 2 """This is the docstring for python parameter "b".""" c: int = field(default=5, kw_only=True) # pyright: ignore @app.default def my_default_command(config: Annotated[Config, Parameter(name="*")]): print(f"{config=}") with pytest.raises(UnusedCliTokensError): app.parse_args("100 200 300", exit_on_error=False) def test_bind_dataclass_tuple_in_var_args(app, assert_parse_args): @dataclass class Square: center: tuple[float, float] side_length: float @app.default def my_default_command(*squares: Square): pass assert_parse_args(my_default_command, "10 20 30", Square(center=(10.0, 20.0), side_length=30.0)) cyclopts-3.9.0/tests/test_bind_dict.py000066400000000000000000000014531475451620500200710ustar00rootroot00000000000000from typing import Dict import pytest @pytest.mark.parametrize( "type_", [ Dict[str, str], dict, Dict, ], ) def test_bind_dict_str_to_str(app, assert_parse_args, type_): @app.command def foo(d: type_): # pyright: ignore pass assert_parse_args(foo, "foo --d.key_1='val1' --d.key-2='val2'", d={"key_1": "val1", "key-2": "val2"}) def test_bind_dict_str_to_int_typing(app, assert_parse_args): @app.command def foo(d: Dict[str, int]): pass assert_parse_args(foo, "foo --d.key1=7 --d.key2=42", d={"key1": 7, "key2": 42}) def test_bind_dict_str_to_int_builtin(app, assert_parse_args): @app.command def foo(d: Dict[str, int]): pass assert_parse_args(foo, "foo --d.key1=7 --d.key2=42", d={"key1": 7, "key2": 42}) cyclopts-3.9.0/tests/test_bind_empty_iterable.py000066400000000000000000000020141475451620500221450ustar00rootroot00000000000000from typing import List, Optional, Set import pytest @pytest.mark.parametrize( "cmd_str,expected", [ ("", None), ("--empty-my-list", []), ("--empty-my-list=True", []), ("--empty-my-list=False", None), ], ) def test_optional_list_empty_flag_default(app, cmd_str, expected, assert_parse_args): @app.default def foo(my_list: Optional[List[int]] = None): pass if expected is None: assert_parse_args(foo, cmd_str) else: assert_parse_args(foo, cmd_str, expected) @pytest.mark.parametrize( "cmd_str,expected", [ ("", None), ("--empty-my-set", set()), ("--empty-my-set=True", set()), ("--empty-my-set=False", None), ], ) def test_optional_set_empty_flag_default(app, cmd_str, expected, assert_parse_args): @app.default def foo(my_set: Optional[Set[int]] = None): pass if expected is None: assert_parse_args(foo, cmd_str) else: assert_parse_args(foo, cmd_str, expected) cyclopts-3.9.0/tests/test_bind_env_var.py000066400000000000000000000030141475451620500206010ustar00rootroot00000000000000from typing import Annotated import pytest from cyclopts import MissingArgumentError, Parameter def test_env_var_unset_use_signature_default(app, assert_parse_args, monkeypatch): @app.default def foo(bar: Annotated[int, Parameter(env_var="BAR")] = 123): pass monkeypatch.delenv("BAR", raising=False) assert_parse_args(foo, "") def test_env_var_set_use_env_var(app, assert_parse_args, monkeypatch): @app.default def foo(bar: Annotated[int, Parameter(env_var="BAR")] = 123): pass monkeypatch.setenv("BAR", "456") assert_parse_args(foo, "", 456) def test_env_var_set_use_env_var_no_default(app, assert_parse_args, monkeypatch): @app.default def foo(bar: Annotated[int, Parameter(env_var="BAR")]): pass monkeypatch.setenv("BAR", "456") assert_parse_args(foo, "", 456) monkeypatch.delenv("BAR") with pytest.raises(MissingArgumentError): app.parse_args([], exit_on_error=False) def test_env_var_list_set_use_env_var(app, assert_parse_args, monkeypatch): @app.default def foo(bar: Annotated[int, Parameter(env_var=["BAR", "BAZ"])] = 123): pass monkeypatch.setenv("BAR", "456") assert_parse_args(foo, [], 456) def test_env_var_unset_list_use_signature_default(app, assert_parse_args, monkeypatch): @app.default def foo(bar: Annotated[int, Parameter(env_var=["BAR", "BAZ"])] = 123): pass monkeypatch.delenv("BAR", raising=False) monkeypatch.delenv("BAZ", raising=False) assert_parse_args(foo, []) cyclopts-3.9.0/tests/test_bind_generic_class.py000066400000000000000000000200511475451620500217420ustar00rootroot00000000000000from textwrap import dedent from typing import Annotated, Dict, Literal, Optional import pytest from cyclopts import Parameter from cyclopts.exceptions import MissingArgumentError class Outfit: def __init__(self, body: str, head: str): self.body = body self.head = head def __eq__(self, other): if not isinstance(other, type(self)): return False return self.body == other.body and self.head == other.head class User: def __init__( self, id: int, name: str = "John Doe", tastes: Optional[Dict[str, int]] = None, outfit: Optional[Annotated[Outfit, Parameter(accepts_keys=True)]] = None, ): self.id = id self.name = name self.tastes = tastes if tastes is not None else {} self.outfit = outfit def __eq__(self, other): if not isinstance(other, type(self)): return False return ( self.id == other.id and self.name == other.name and self.tastes == other.tastes and self.outfit == other.outfit ) def test_bind_generic_class_accepts_keys_true(app, assert_parse_args): @app.command def foo(user: Annotated[User, Parameter(accepts_keys=True)]): pass assert_parse_args( foo, "foo --user.id=123 --user.tastes.wine=9 --user.tastes.cheese=7 --user.tastes.cabbage=1 --user.outfit.body=t-shirt --user.outfit.head=baseball-cap", User(id=123, tastes={"wine": 9, "cheese": 7, "cabbage": 1}, outfit=Outfit(body="t-shirt", head="baseball-cap")), ) def test_bind_generic_class_accepts_keys_none_1_args(app, assert_parse_args, console): class User: def __init__(self, age: int): self.age = age def __eq__(self, other): if not isinstance(other, type(self)): return False return self.age == other.age @app.command def foo(user: User): pass assert_parse_args(foo, "foo 100", User(100)) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_generic_class foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * USER.AGE --user.age [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_generic_class_accepts_keys_false_1_args(app, assert_parse_args, console): class User: def __init__(self, age: int): self.age = age def __eq__(self, other): if not isinstance(other, type(self)): return False return self.age == other.age @app.command def foo(user: Annotated[User, Parameter(accepts_keys=False)]): pass assert_parse_args(foo, "foo 100", User(100)) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_generic_class foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * USER --user [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected class Coordinates: def __init__( self, x: float, y: float, *, color: Literal["red", "green", "blue"] = "red", ): self.x = x self.y = y self.color = color def __eq__(self, other): if not isinstance(other, type(self)): return False return self.x == other.x and self.y == other.y and self.color == other.color def test_bind_generic_class_accepts_default_multiple_args(app, assert_parse_args, console): @app.command def foo(coords: Coordinates, priority: int): pass assert_parse_args(foo, "foo 100 200 7", Coordinates(100, 200), 7) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_generic_class foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * COORDS.X --coords.x [required] │ │ * COORDS.Y --coords.y [required] │ │ * PRIORITY --priority [required] │ │ --coords.color [choices: red, green, blue] [default: red] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_generic_class_accepts_false_multiple_args(app, assert_parse_args, console): @app.command def foo(coords: Annotated[Coordinates, Parameter(accepts_keys=False)]): pass assert_parse_args(foo, "foo 100 200", Coordinates(100, 200)) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_bind_generic_class foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * COORDS --coords [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_generic_class_keyword_with_positional_only_subkeys(app, console, assert_parse_args): """This test has a keyword-only parameter that has position-only subkeys, which are skipped.""" class User: def __init__(self, name: str, age: int, /): self.name = name self.age = age def __eq__(self, other): if not isinstance(other, type(self)): return False return self.name == other.name and self.age == other.age @app.command def foo(*, user: User): pass assert_parse_args(foo, "foo --user Bob 30", user=User("Bob", 30)) with console.capture() as capture: app("foo --help", console=console) actual = capture.get() # No arguments/parameters expected = dedent( """\ Usage: test_bind_generic_class foo [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * --user [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected with pytest.raises(MissingArgumentError): app("foo --user.name=Bob --user.age=100", exit_on_error=False) cyclopts-3.9.0/tests/test_bind_kwargs.py000066400000000000000000000013161475451620500204420ustar00rootroot00000000000000from typing import List def test_kwargs_list_int(app, assert_parse_args): @app.command def foo(a: int, **kwargs: List[int]): pass assert_parse_args(foo, "foo 1 --bar=2 --baz=4 --bar 3", 1, bar=[2, 3], baz=[4]) def test_kwargs_int(app, assert_parse_args): @app.command def foo(a: int, **kwargs: int): pass assert_parse_args(foo, "foo 1 --bar=2 --baz 3", 1, bar=2, baz=3) assert_parse_args(foo, "foo 1", 1) def test_args_and_kwargs_int(app, assert_parse_args): @app.command def foo(a: int, *args: int, **kwargs: int): pass assert_parse_args(foo, "foo 1 2 3 4 5 --bar=2 --baz 3", 1, 2, 3, 4, 5, bar=2, baz=3) assert_parse_args(foo, "foo 1", 1) cyclopts-3.9.0/tests/test_bind_list.py000066400000000000000000000057401475451620500201240ustar00rootroot00000000000000from pathlib import Path from typing import List, Optional, Sequence, Tuple import pytest from cyclopts.exceptions import MissingArgumentError def test_pos_list(app, assert_parse_args): @app.command def foo(a: List[int]): pass assert_parse_args(foo, "foo 1 2 3", [1, 2, 3]) def test_keyword_list(app, assert_parse_args): @app.command def foo(a: List[int]): pass assert_parse_args(foo, "foo --a=1 --a=2 --a 3", [1, 2, 3]) def test_keyword_list_mutable_default(app, assert_parse_args): @app.command def foo(a: List[int] = []): # noqa: B006 pass assert_parse_args(foo, "foo --a=1 --a=2 --a 3", [1, 2, 3]) assert_parse_args(foo, "foo") def test_keyword_list_pos(app, assert_parse_args): @app.command def foo(a: List[int]): pass assert_parse_args(foo, "foo 1 2 3", [1, 2, 3]) def test_keyword_optional_list_none_default(app, assert_parse_args): @app.command def foo(a: Optional[List[int]] = None): pass assert_parse_args(foo, "foo") @pytest.mark.parametrize( "cmd_expected", [ ("", None), ("--verbose", [True]), ("--verbose --verbose", [True, True]), ("--verbose --verbose --no-verbose", [True, True, False]), ("--verbose --verbose=False", [True, False]), ("--verbose --no-verbose=False", [True, True]), ("--verbose --verbose=True", [True, True]), ], ) def test_keyword_list_of_bool(app, assert_parse_args, cmd_expected): cmd, expected = cmd_expected @app.default def foo(*, verbose: Optional[list[bool]] = None): pass if expected is None: assert_parse_args(foo, cmd) else: assert_parse_args(foo, cmd, verbose=expected) @pytest.mark.parametrize( "cmd", [ "foo --item", ], ) def test_list_tuple_missing_arguments_no_arguments(app, cmd): """Missing values.""" @app.command def foo(item: List[Tuple[int, str]]): pass with pytest.raises(MissingArgumentError): app(cmd, exit_on_error=False) @pytest.mark.parametrize( "cmd", [ "foo --item 1", "foo --item a --stuff g", ], ) def test_list_tuple_missing_arguments_non_divisible(app, cmd): """Missing values.""" @app.command def foo(item: List[Tuple[int, str]], stuff: str = ""): pass with pytest.raises(MissingArgumentError): app(cmd, exit_on_error=False) def test_pos_sequence(app, assert_parse_args): @app.command def foo(a: Sequence[int]): pass assert_parse_args(foo, "foo 1 2 3", [1, 2, 3]) @pytest.mark.parametrize( "cmd_str", [ "fizz buzz bar", "-- fizz buzz bar", "fizz -- buzz bar", "fizz buzz -- bar", "fizz buzz bar --", ], ) def test_list_positional_all_but_last(app, cmd_str, assert_parse_args): @app.default def foo(inputs: list[Path], output: Path, /): pass assert_parse_args(foo, cmd_str, [Path("fizz"), Path("buzz")], Path("bar")) cyclopts-3.9.0/tests/test_bind_namedtuple.py000066400000000000000000000036011475451620500213010ustar00rootroot00000000000000import sys from collections import namedtuple from typing import NamedTuple import pytest def test_bind_typing_named_tuple(app, assert_parse_args): class Employee(NamedTuple): name: str id: int = 3 @app.command def foo(user: Employee): pass assert_parse_args( foo, 'foo --user.name="John Smith" --user.id=100', Employee(name="John Smith", id=100), ) assert_parse_args( foo, 'foo "John Smith" 100', Employee(name="John Smith", id=100), ) def test_bind_typing_named_tuple_var_positional(app, assert_parse_args): class Employee(NamedTuple): name: str id: int @app.command def foo(*users: Employee): pass assert_parse_args( foo, 'foo "John Smith" 100 "Mary Jones" 200', Employee(name="John Smith", id=100), Employee(name="Mary Jones", id=200), ) @pytest.mark.skipif(sys.version_info < (3, 10), reason="<3.10 does not have __annotations__ field.") def test_bind_collections_named_tuple(app, assert_parse_args): # All fields will be strings since cyclopts doesn't know the types. Employee = namedtuple("Employee", ["name", "id"]) @app.command def foo(user: Employee): pass assert_parse_args( foo, 'foo --user.name="John Smith" --user.id=100', Employee(name="John Smith", id="100"), ) @pytest.mark.skipif(sys.version_info > (3, 9), reason="namedtuple fully supported.") def test_bind_collections_named_tuple_unsupported(app, assert_parse_args): # All fields will be strings since cyclopts doesn't know the types. Employee = namedtuple("Employee", ["name", "id"]) @app.command def foo(user: Employee): pass with pytest.raises(ValueError): assert_parse_args( foo, 'foo --user.name="John Smith" --user.id=100', ) cyclopts-3.9.0/tests/test_bind_no_parse.py000066400000000000000000000012121475451620500207450ustar00rootroot00000000000000from typing import Annotated import pytest from cyclopts import Parameter def test_no_parse_pos(app, assert_parse_args_partial): @app.default def foo(buzz: str, *, fizz: Annotated[str, Parameter(parse=False)]): pass assert_parse_args_partial(foo, "buzz_value", "buzz_value") _, _, ignored = app.parse_args("buzz_value") assert ignored == {"fizz": str} def test_no_parse_invalid_kind(app): # Parameter.parse=False must be used with KEYWORD_ONLY. with pytest.raises(ValueError): @app.default def foo(buzz: str, fizz: Annotated[str, Parameter(parse=False)]): pass app([]) cyclopts-3.9.0/tests/test_bind_pos_only.py000066400000000000000000000042001475451620500210010ustar00rootroot00000000000000import sys import pytest from cyclopts import MissingArgumentError, UnknownOptionError @pytest.mark.parametrize( "cmd_str", [ "foo 1 2 3 4 5", ], ) def test_star_args(app, cmd_str, assert_parse_args): @app.command def foo(a: int, b: int, *args: int): pass assert_parse_args(foo, cmd_str, 1, 2, 3, 4, 5) @pytest.mark.parametrize( "cmd_str", [ "foo 1 2 3", ], ) def test_pos_only(app, cmd_str, assert_parse_args): @app.command def foo(a: int, b: int, c: int, /): pass assert_parse_args(foo, cmd_str, 1, 2, 3) @pytest.mark.parametrize( "cmd_str_e", [ ("foo 1 2 --c=3", UnknownOptionError), # Unknown option "--c" ], ) def test_pos_only_exceptions(app, cmd_str_e): cmd_str, e = cmd_str_e @app.command def foo(a: int, b: int, c: int, /): pass with pytest.raises(e): app.parse_args(cmd_str, print_error=False, exit_on_error=False) @pytest.mark.parametrize( "cmd_str", [ "foo 1 2 3 4", "foo 1 2 3 --d 4", "foo 1 2 --d=4 3", ], ) def test_pos_only_extended(app, cmd_str, assert_parse_args): @app.command def foo(a: int, b: int, c: int, /, d: int): pass assert_parse_args(foo, cmd_str, 1, 2, 3, 4) @pytest.mark.parametrize( "cmd_str_e", [ ("foo 1 2 3", MissingArgumentError), ("foo 1 2", MissingArgumentError), ], ) def test_pos_only_extended_exceptions(app, cmd_str_e): cmd_str, e = cmd_str_e @app.command def foo(a: int, b: int, c: int, /, d: int): pass with pytest.raises(e): app.parse_args(cmd_str, print_error=False, exit_on_error=False) @pytest.mark.skipif( sys.version_info < (3, 10), reason="https://peps.python.org/pep-0563/ Postponed Evaluation of Annotations" ) @pytest.mark.parametrize( "cmd_str", [ "foo a 2 3 4", "foo a 2 3 --d 4", "foo a 2 --d=4 3", ], ) def test_pos_only_extended_str_type(app, cmd_str, assert_parse_args): @app.command def foo(a: "str", b: "int", c: int, /, d: "int"): pass assert_parse_args(foo, cmd_str, "a", 2, 3, 4) cyclopts-3.9.0/tests/test_bind_tuple.py000066400000000000000000000053751475451620500203060ustar00rootroot00000000000000from typing import List, Tuple import pytest from cyclopts.exceptions import MissingArgumentError @pytest.mark.parametrize( "cmd_str", [ "1 2 80 160 255", "--coordinates 1 2 --color 80 160 255", "--color 80 160 255 --coordinates 1 2", "--color 80 160 255 --coordinates=1 2", ], ) def test_bind_tuple_basic(app, cmd_str, assert_parse_args): @app.default def foo(coordinates: Tuple[int, int], color: Tuple[int, int, int]): pass assert_parse_args(foo, cmd_str, (1, 2), (80, 160, 255)) @pytest.mark.parametrize( "cmd_str", [ "1 2 alice 100 200", "--coordinates 1 2 --data alice 100 200", "--data alice 100 200 --coordinates 1 2", ], ) def test_bind_tuple_nested(app, cmd_str, assert_parse_args): @app.default def foo(coordinates: Tuple[int, int], data: Tuple[Tuple[str, int], int]): pass assert_parse_args(foo, cmd_str, (1, 2), (("alice", 100), 200)) @pytest.mark.parametrize( "cmd_str", [ "1 2 alice 100 bob 200", "--coordinates 1 2 --data alice 100 --data bob 200", "--data alice 100 --coordinates 1 2 --data bob 200", ], ) def test_bind_tuple_ellipsis(app, cmd_str, assert_parse_args): @app.default def foo(coordinates: Tuple[int, int], data: Tuple[Tuple[str, int], ...]): pass assert_parse_args(foo, cmd_str, (1, 2), (("alice", 100), ("bob", 200))) @pytest.mark.parametrize( "cmd_str", [ "1 2 3", "--values 1 --values 2 --values 3", ], ) def test_bind_tuple_no_inner_types(app, cmd_str, assert_parse_args): @app.default def foo(values: Tuple): pass # Interpreted as a string because: # 1. Tuple -> Tuple[Any, ...] # 2. Any is treated the same as no annotation. # 3. Even if a default value was supplied, we couldn't unambiguously infer a type. # 4. This falls back to string. assert_parse_args(foo, cmd_str, ("1", "2", "3")) @pytest.mark.parametrize( "cmd_str", [ "1", "--coordinates 1", ], ) def test_bind_tuple_insufficient_tokens(app, cmd_str): @app.default def foo(coordinates: Tuple[int, int]): pass with pytest.raises(MissingArgumentError): app.parse_args(cmd_str, print_error=False, exit_on_error=False) @pytest.mark.parametrize( "cmd_str", [ "--coordinates 1 2 --color 80 160 255 --coordinates 3 4", "--coordinates 1 2 --coordinates 3 4 --color 80 160 255", "1 2 3 4 --color 80 160 255", ], ) def test_bind_list_of_tuple(app, cmd_str, assert_parse_args): @app.default def foo(coordinates: List[Tuple[int, int]], color: Tuple[int, int, int]): pass assert_parse_args(foo, cmd_str, [(1, 2), (3, 4)], (80, 160, 255)) cyclopts-3.9.0/tests/test_bind_typed_dict.py000066400000000000000000000174551475451620500213070ustar00rootroot00000000000000import sys from textwrap import dedent from typing import Annotated, List, TypedDict import pytest from cyclopts import MissingArgumentError, Parameter from cyclopts.exceptions import UnknownOptionError if sys.version_info < (3, 11): # pragma: no cover from typing_extensions import NotRequired, Required else: # pragma: no cover from typing import NotRequired, Required class MyDict(TypedDict): my_int: int my_str: str my_list: list my_list_int: List[int] def test_bind_typed_dict(app, assert_parse_args): @app.command def foo(d: MyDict): pass assert_parse_args( foo, "foo --d.my-int=5 --d.my-str=bar --d.my-list=a --d.my-list=b --d.my-list-int=1 --d.my-list-int=2", d={ "my_int": 5, "my_str": "bar", "my_list": ["a", "b"], "my_list_int": [1, 2], }, ) def test_bind_typed_dict_missing_arg_basic(app, console): @app.command def foo(d: MyDict): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app( "foo --d.my-int=5 --d.my-str=bar", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--d.my-list" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_typed_dict_missing_arg_flatten(app, console): @app.command def foo(d: Annotated[MyDict, Parameter(name="*")]): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app( "foo", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--my-int" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_typed_dict_missing_arg_renamed_no_hyphen(app, console): class MyDict(TypedDict): my_int: int my_str: str my_list: Annotated[list, Parameter(name="your-list")] my_list_int: List[int] @app.command def foo(d: MyDict): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app( "foo --d.my-int=5 --d.my-str=bar", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--d.your-list" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_typed_dict_missing_arg_renamed_hyphen(app, console): class MyDict(TypedDict): my_int: int my_str: str my_list: Annotated[list, Parameter(name="--your-list")] my_list_int: List[int] @app.command def foo(d: MyDict): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app( "foo --d.my-int=5 --d.my-str=bar", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--your-list" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_typed_dict_missing_arg_nested(app, console): class User(TypedDict): name: str age: int class MyDict(TypedDict): my_int: int my_str: str my_user: User @app.command def foo(d: MyDict): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app( "foo --d.my-int=5 --d.my-str=bar --d.my-user.age=30", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--d.my-user.name" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_typed_dict_total_false(app, assert_parse_args): class MyDict(TypedDict, total=False): my_int: int my_str: str @app.command def foo(d: MyDict): pass assert_parse_args(foo, "foo --d.my-str=bar", d={"my_str": "bar"}) def test_bind_typed_dict_not_required(app, assert_parse_args): class MyDict(TypedDict): my_int: int my_str: NotRequired[str] @app.command def foo(d: MyDict): pass assert_parse_args(foo, "foo --d.my-int=5", d={"my_int": 5}) def test_bind_typed_dict_required(app, assert_parse_args): class MyDict(TypedDict, total=False): my_int: Required[int] my_str: str @app.command def foo(d: MyDict): pass assert_parse_args(foo, "foo --d.my-int=5", d={"my_int": 5}) def test_bind_typed_dict_extra_field(app, console): @app.command def foo(d: MyDict): pass with console.capture() as capture, pytest.raises(UnknownOptionError): app.parse_args( "foo --d.my-int=5 --d.my-str=bar --d.my-list=a --d.my-list=b --d.my-list-int=1 --d.my-list-int=2 --d.extra-key=10", console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Unknown option: "--d.extra-key". │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected cyclopts-3.9.0/tests/test_bind_union.py000066400000000000000000000035051475451620500202760ustar00rootroot00000000000000from textwrap import dedent from typing import Annotated, Union import pytest from cyclopts import Parameter from cyclopts.exceptions import CoercionError @pytest.mark.parametrize( "cmd_str,expected", [ ("foo 1", 1), ("foo --a=1", 1), ("foo --a 1", 1), ("foo bar", "bar"), ("foo --a=bar", "bar"), ("foo --a bar", "bar"), ], ) @pytest.mark.parametrize("annotated", [False, True]) def test_union_required_implicit_coercion(app, cmd_str, expected, annotated, assert_parse_args): """ For a union without an explicit coercion, the first non-None type annotation should be used. In this case, it's ``int``. """ if annotated: @app.command def foo(a: Annotated[Union[None, int, str], Parameter(help="help for a")]): pass else: @app.command def foo(a: Union[None, int, str]): pass assert_parse_args(foo, cmd_str, expected) def test_union_coercion_cannot_coerce_error(app, console): @app.default def default(a: Union[None, int, float]): pass with console.capture() as capture, pytest.raises(CoercionError): app.parse_args("foo", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid value for "A": unable to convert "foo" into int|float. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected cyclopts-3.9.0/tests/test_bind_var_pos.py000066400000000000000000000003371475451620500206170ustar00rootroot00000000000000def test_bind_var_pos(app): """Checks if "Alice" gets erroneously unpacked into ``("A", "l", "i", "c", "e")``.""" @app.default def default(*tokens: str): assert tokens == ("Alice",) app(["Alice"]) cyclopts-3.9.0/tests/test_coercion.py000066400000000000000000000226521475451620500177570ustar00rootroot00000000000000import inspect import sys from enum import Enum, auto from pathlib import Path from typing import Annotated, Any, Iterable, List, Literal, Optional, Sequence, Set, Tuple, Union from unittest.mock import Mock import pytest from cyclopts import CoercionError, Token from cyclopts._convert import convert, token_count def _assert_tuple(expected, actual): assert type(actual) is tuple assert len(expected) == len(actual) for e, a in zip(expected, actual): assert type(e) is type(a) assert e == a def test_token_count_tuple_basic(): assert (3, False) == token_count(Tuple[int, int, int]) def test_token_count_tuple_no_inner_type(): assert (1, True) == token_count(Tuple) assert (1, True) == token_count(tuple) def test_token_count_tuple_nested(): assert (4, False) == token_count(Tuple[Tuple[int, int], int, int]) def test_token_count_tuple_ellipsis(): assert (1, True) == token_count(Tuple[int, ...]) def test_token_count_tuple_ellipsis_nested(): assert (2, True) == token_count(Tuple[Tuple[int, int], ...]) def test_token_union(): assert (1, False) == token_count(Union[None, int]) def test_token_count_standard(): assert (1, False) == token_count(int) def test_token_count_bool(): assert (0, False) == token_count(bool) def test_token_count_list(): assert (1, True) == token_count(List[int]) def test_token_count_sequence(): assert (1, True) == token_count(Sequence[int]) assert (2, True) == token_count(Sequence[Tuple[int, int]]) def test_token_count_list_generic(): assert (1, True) == token_count(list) def test_token_count_list_direct(): assert (1, True) == token_count(list[int]) # pyright: ignore def test_token_count_list_of_tuple(): assert (3, True) == token_count(List[Tuple[int, int, int]]) def test_token_count_list_of_tuple_nested(): assert (4, True) == token_count(List[Tuple[Tuple[int, int], int, int]]) def test_token_count_iterable(): assert (1, True) == token_count(Iterable[int]) assert (2, True) == token_count(Iterable[Tuple[int, int]]) def test_token_count_union(): assert (1, False) == token_count(Union[int, str, float]) def test_token_count_union_error(): with pytest.raises(ValueError): assert (1, False) == token_count(Union[int, Tuple[int, int]]) def test_coerce_no_tokens(): with pytest.raises(ValueError): convert(int, []) def test_coerce_bool(): assert True is convert(bool, ["true"]) assert False is convert(bool, ["false"]) def test_coerce_error(): with pytest.raises(CoercionError): convert(bool, ["foo"]) def test_coerce_int(): assert 123 == convert(int, ["123"]) def test_coerce_annotated_int(): assert [123, 456] == convert(Annotated[int, "foo"], ["123", "456"]) assert [123, 456] == convert(Annotated[List[int], "foo"], ["123", "456"]) def test_coerce_optional_annotated_int(): assert [123, 456] == convert(Optional[Annotated[int, "foo"]], ["123", "456"]) assert [123, 456] == convert(Optional[Annotated[List[int], "foo"]], ["123", "456"]) def test_coerce_annotated_union_str_secondary_choice(): assert 123 == convert(Union[None, int, str], ["123"]) assert "foo" == convert(Union[None, int, str], ["foo"]) with pytest.raises(CoercionError): convert(Union[None, int, float], ["invalid-choice"]) def test_coerce_annotated_nested_union_str_secondary_choice(): assert 123 == convert(Union[None, Union[int, str]], ["123"]) assert "foo" == convert(Union[None, Union[int, str]], ["foo"]) def test_coerce_annotated_union_int(): assert 123 == convert(Annotated[Union[None, int, float], "foo"], ["123"]) assert [123, 456] == convert(Annotated[int, "foo"], ["123", "456"]) assert [123, 456] == convert(Annotated[Union[None, int, float], "foo"], ["123", "456"]) def test_coerce_enum(): class SoftwareEnvironment(Enum): DEV = auto() STAGING = auto() PROD = auto() _PROD_OLD = auto() # tests case-insensitivity assert SoftwareEnvironment.STAGING == convert(SoftwareEnvironment, ["staging"]) # tests underscore/hyphen support assert SoftwareEnvironment._PROD_OLD == convert(SoftwareEnvironment, ["prod_old"]) assert SoftwareEnvironment._PROD_OLD == convert(SoftwareEnvironment, ["prod-old"]) with pytest.raises(CoercionError): convert(SoftwareEnvironment, ["invalid-choice"]) def test_coerce_tuple_basic_single(): _assert_tuple((1,), convert(Tuple[int], ["1"])) def test_coerce_tuple_str_single(): _assert_tuple(("foo",), convert(Tuple[str], ["foo"])) def test_coerce_tuple_basic_double(): _assert_tuple((1, 2.0), convert(Tuple[int, Union[None, float, int]], ["1", "2"])) def test_coerce_tuple_typing_no_inner_types(): _assert_tuple(("1", "2"), convert(Tuple, ["1", "2"])) def test_coerce_tuple_builtin_no_inner_types(): _assert_tuple(("1", "2"), convert(tuple, ["1", "2"])) def test_coerce_tuple_nested(): _assert_tuple( (1, (2.0, "foo")), convert(Tuple[int, Tuple[float, Union[None, str, int]]], ["1", "2", "foo"]), ) def test_coerce_tuple_len_mismatch_underflow(): with pytest.raises(CoercionError): convert(Tuple[int, int], ["1"]) def test_coerce_tuple_len_mismatch_overflow(): with pytest.raises(CoercionError): convert(Tuple[int, int], ["1", "2", "3"]) @pytest.mark.skipif(sys.version_info < (3, 11), reason="Typing") def test_coerce_tuple_ellipsis_too_many_inner_types(): with pytest.raises(ValueError): # This is a ValueError because it happens prior to runtime. # Only 1 inner type annotation allowed convert(Tuple[int, int, ...], ["1", "2"]) # pyright: ignore def test_coerce_tuple_ellipsis_non_divisible(): with pytest.raises(CoercionError): convert(Tuple[Tuple[int, int], ...], ["1", "2", "3"]) def test_coerce_list(): assert [123, 456] == convert(int, ["123", "456"]) assert [123, 456] == convert(List[int], ["123", "456"]) assert [123] == convert(List[int], ["123"]) def test_coerce_list_of_tuple_str_single_1(): res = convert(List[Tuple[str]], ["foo"]) assert isinstance(res, list) assert len(res) == 1 _assert_tuple(("foo",), res[0]) def test_coerce_list_of_tuple_str_single_2(): res = convert(List[Tuple[str]], ["foo", "bar"]) assert isinstance(res, list) assert len(res) == 2 _assert_tuple(("foo",), res[0]) _assert_tuple(("bar",), res[1]) def test_coerce_bare_list(): # Implicit element type: str assert ["123", "456"] == convert(list, ["123", "456"]) def test_coerce_iterable(): assert [123, 456] == convert(Iterable[int], ["123", "456"]) assert [123] == convert(Iterable[int], ["123"]) def test_coerce_set(): assert {"123", "456"} == convert(Set[str], ["123", "456"]) assert {123, 456} == convert(Set[Union[int, str]], ["123", "456"]) def test_coerce_frozenset(): assert frozenset({"123", "456"}) == convert(frozenset[str], ["123", "456"]) assert frozenset({123, 456}) == convert(frozenset[Union[int, str]], ["123", "456"]) def test_coerce_literal(): assert "foo" == convert(Literal["foo", "bar", 3], ["foo"]) assert "bar" == convert(Literal["foo", "bar", 3], ["bar"]) assert 3 == convert(Literal["foo", "bar", 3], ["3"]) def assert_convert_coercion_error(*args, msg, **kwargs): mock_argument = Mock() mock_argument.name = "mocked_argument_name" with pytest.raises(CoercionError) as e: try: convert(*args, **kwargs) except CoercionError as coercion_error: coercion_error.argument = mock_argument raise exception_message = str(e.value).split("\n", 1)[1] assert exception_message == msg def test_coerce_literal_invalid_choice(): assert_convert_coercion_error( Literal["foo", "bar", 3], ["invalid-choice"], msg="""Invalid value for "MOCKED_ARGUMENT_NAME": unable to convert "invalid-choice" into one of {'foo', 'bar', 3}.""", ) def test_coerce_literal_invalid_choice_keyword(): assert_convert_coercion_error( Literal["foo", "bar", 3], [Token(keyword="--MY_KEYWORD", value="invalid-choice")], msg="""Invalid value for "--MY_KEYWORD": unable to convert "invalid-choice" into one of {'foo', 'bar', 3}.""", ) def test_coerce_literal_invalid_choice_non_cli_token(): assert_convert_coercion_error( Literal["foo", "bar", 3], [Token(value="invalid-choice", source="TEST")], msg="""Invalid value for "MOCKED_ARGUMENT_NAME" from TEST: unable to convert "invalid-choice" into one of {'foo', 'bar', 3}.""", ) def test_coerce_literal_invalid_choice_keyword_non_cli_token(): assert_convert_coercion_error( Literal["foo", "bar", 3], [Token(keyword="--MY-KEYWORD", value="invalid-choice", source="TEST")], msg="""Invalid value for "--MY-KEYWORD" from TEST: unable to convert "invalid-choice" into one of {'foo', 'bar', 3}.""", ) def test_coerce_path(): assert Path("foo") == convert(Path, ["foo"]) def test_coerce_any(): assert "foo" == convert(Any, ["foo"]) def test_coerce_bytes(): assert b"foo" == convert(bytes, ["foo"]) assert [b"foo", b"bar"] == convert(bytes, ["foo", "bar"]) def test_coerce_bytearray(): res = convert(bytearray, ["foo"]) assert isinstance(res, bytearray) assert bytearray(b"foo") == res assert [bytearray(b"foo"), bytearray(b"bar")] == convert(bytearray, ["foo", "bar"]) def test_coerce_parameter_kind_empty(): assert "foo" == convert(inspect.Parameter.empty, ["foo"]) cyclopts-3.9.0/tests/test_command_collision.py000066400000000000000000000017011475451620500216370ustar00rootroot00000000000000import pytest from cyclopts import CommandCollisionError def test_command_collision(app): @app.command def foo(): pass with pytest.raises(CommandCollisionError): @app.command def foo(): # noqa: F811 pass with pytest.raises(CommandCollisionError): @app.command(name="foo") def bar(): pass def test_command_collision_meta(app): @app.command def foo(): pass with pytest.raises(CommandCollisionError): @app.meta.command def foo(): # noqa: F811 pass with pytest.raises(CommandCollisionError): @app.meta.command(name="foo") def bar(): pass def test_command_collision_default(app): """Cannot register multiple functions to default.""" @app.default def foo(): pass with pytest.raises(CommandCollisionError): @app.default def bar(): pass cyclopts-3.9.0/tests/test_console.py000066400000000000000000000041061475451620500176120ustar00rootroot00000000000000from contextlib import suppress import pytest from cyclopts import App, CycloptsError from cyclopts.exceptions import UnusedCliTokensError @pytest.fixture def subapp(app): app.command(subapp := App(name="foo")) return subapp @pytest.mark.parametrize("cmd", ["foo --help", "foo invalid-command"]) def test_root_console(app, mocker, cmd): app.console = mocker.MagicMock() with suppress(CycloptsError): app(cmd, exit_on_error=False) app.console.print.assert_called() @pytest.mark.parametrize("cmd", ["foo --help", "foo invalid-command"]) def test_root_console_subapp(app, subapp, mocker, cmd): """Check if root console is properly resolved (subapp.console not specified).""" app.console = mocker.MagicMock() with suppress(CycloptsError): app(cmd, exit_on_error=False) app.console.print.assert_called() @pytest.mark.parametrize("cmd", ["foo --help", "foo invalid-command"]) def test_root_subapp_console(app, subapp, mocker, cmd): """Check if subapp console is properly resolved (NOT app.console).""" app.console = mocker.MagicMock() subapp.console = mocker.MagicMock() with suppress(CycloptsError): app(cmd, exit_on_error=False) app.console.print.assert_not_called() subapp.console.print.assert_called() @pytest.mark.parametrize("cmd", ["foo --help", "foo invalid-command"]) def test_root_subapp_arg_console(app, subapp, mocker, cmd): """Explicitly provided console should be used.""" console = mocker.MagicMock() app.console = mocker.MagicMock() subapp.console = mocker.MagicMock() with suppress(CycloptsError): app(cmd, console=console, exit_on_error=False) console.print.assert_called() app.console.print.assert_not_called() subapp.console.print.assert_not_called() def test_console_populated_issue_103(app): """Ensures console is populated for an UnusedCliTokensError. https://github.com/BrianPugh/cyclopts/issues/103 """ @app.command def foo(): pass with pytest.raises(UnusedCliTokensError): app("foo bar", exit_on_error=False) cyclopts-3.9.0/tests/test_edit.py000066400000000000000000000124341475451620500171000ustar00rootroot00000000000000import subprocess from pathlib import Path import pytest from cyclopts._edit import ( EditorDidNotChangeError, EditorDidNotSaveError, EditorError, EditorNotFoundError, edit, ) @pytest.fixture def mock_editor(): """Mock editor that simulates saving file with edited content.""" def fake_editor(args): Path(args[1]).write_text("edited content") return 0 return fake_editor def test_basic_edit(mocker, mock_editor, monkeypatch): """Test basic editing functionality.""" monkeypatch.setenv("EDITOR", "test_editor") mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=mock_editor) result = edit("initial text") assert result == "edited content" def test_custom_path(mocker, mock_editor, tmp_path, monkeypatch): """Test editing with custom path.""" custom_path = tmp_path / "custom.txt" monkeypatch.setenv("EDITOR", "test_editor") mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=mock_editor) result = edit("initial text", path=custom_path) assert result == "edited content" def test_editor_not_found(mocker, monkeypatch): """Test behavior when no editor is found.""" monkeypatch.delenv("EDITOR", raising=False) mocker.patch("shutil.which", return_value=False) with pytest.raises(EditorNotFoundError): edit("test") def test_did_not_save(mocker, monkeypatch): """Test behavior when user doesn't save.""" monkeypatch.setenv("EDITOR", "test_editor") def fake_editor(_): # Doesn't "save" return 0 mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=fake_editor) with pytest.raises(EditorDidNotSaveError): edit("initial text", save=True) def test_did_not_change(mocker, monkeypatch): """Test behavior when content isn't changed.""" monkeypatch.setenv("EDITOR", "test_editor") initial_text = "unchanged text" def fake_editor(args): assert args[0] == "test_editor" Path(args[1]).write_text(initial_text) return 0 mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=fake_editor) with pytest.raises(EditorDidNotChangeError): edit(initial_text, required=True) def test_editor_error(mocker, monkeypatch): """Test handling of editor errors.""" monkeypatch.setenv("EDITOR", "test_editor") mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=subprocess.CalledProcessError(1, "test_editor")) with pytest.raises(EditorError) as exc_info: edit("test") assert "status 1" in str(exc_info.value) def test_custom_encoding(mocker, tmp_path, monkeypatch): """Test custom encoding support.""" test_path = tmp_path / "test.txt" monkeypatch.setenv("EDITOR", "test_editor") def fake_editor_with_encoding(args): assert args[0] == "test_editor" Path(args[1]).write_text("текст", encoding="utf-16") return 0 mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=fake_editor_with_encoding) result = edit("initial", path=test_path, encoding="utf-16") assert result == "текст" def test_fallback_editors(mocker, mock_editor, monkeypatch): """Test fallback editor selection.""" monkeypatch.delenv("EDITOR", raising=False) def mock_which(editor_name): return editor_name == "vim" mocker.patch("shutil.which", side_effect=mock_which) mock_check_call = mocker.patch("subprocess.check_call", side_effect=mock_editor) result = edit("test", fallback_editors=["emacs", "vim", "nano"]) assert result == "edited content" assert mock_check_call.call_args_list[0].args[0][0] == "vim" def test_editor_args(mocker, monkeypatch): """Test passing additional arguments to editor.""" monkeypatch.setenv("EDITOR", "test_editor") def check_editor_args(args): assert args[0] == "test_editor" assert "--no-splash" in args Path(args[1]).write_text("edited with args") return 0 mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=check_editor_args) result = edit("test", editor_args=["--no-splash"]) assert result == "edited with args" def test_optional_change(mocker, monkeypatch): """Test when content changes are optional.""" monkeypatch.setenv("EDITOR", "test_editor") initial_text = "unchanged" def fake_editor(args): assert args[0] == "test_editor" Path(args[1]).write_text(initial_text) return 0 mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=fake_editor) # Should not raise DidNotChangeError result = edit(initial_text, required=False) assert result == initial_text def test_file_cleanup(mocker, tmp_path, monkeypatch, mock_editor): """Test temporary file cleanup.""" test_path = tmp_path / "cleanup_test.txt" monkeypatch.setenv("EDITOR", "test_editor") mocker.patch("shutil.which", return_value=True) mocker.patch("subprocess.check_call", side_effect=mock_editor) edit("test", path=test_path) assert not test_path.exists() cyclopts-3.9.0/tests/test_env_var.py000066400000000000000000000027121475451620500176110ustar00rootroot00000000000000from pathlib import Path from typing import Annotated, Iterable, List, Optional, Tuple import pytest from cyclopts._env_var import env_var_split def test_env_var_split_path_windows(mocker): mocker.patch("cyclopts._env_var.os.pathsep", ";") assert env_var_split(List[Path], r"C:\foo\bar;D:\fizz\buzz") == [ r"C:\foo\bar", r"D:\fizz\buzz", ] @pytest.mark.parametrize( "type_", [ List[Path], List[Optional[Path]], Tuple[Path, ...], Tuple[Optional[Path], ...], Annotated[List[Path], "test annotation"], ], ) def test_env_var_split_path_posix_multiple(mocker, type_): mocker.patch("cyclopts._env_var.os.pathsep", ":") assert env_var_split(type_, "/foo/bar;:/fizz/buzz") == [ "/foo/bar;", "/fizz/buzz", ] def test_env_var_split_path_posix_single(mocker): """Dont split when a single Path is desired.""" mocker.patch("cyclopts._env_var.os.pathsep", ":") assert ["foo:bar"] == env_var_split(Path, "foo:bar") def test_env_var_split_path_general(): assert ["foo"] == env_var_split(str, "foo") assert ["foo"] == env_var_split(Optional[str], "foo") assert ["foo bar"] == env_var_split(str, "foo bar") assert ["foo", "bar"] == env_var_split(List[str], "foo bar") assert ["foo", "bar"] == env_var_split(Tuple[str, ...], "foo bar") assert ["foo", "bar"] == env_var_split(Iterable[str], "foo bar") assert ["1"] == env_var_split(int, "1") cyclopts-3.9.0/tests/test_exceptions.py000066400000000000000000000300441475451620500203310ustar00rootroot00000000000000from textwrap import dedent from typing import Annotated, Union import pytest from cyclopts import ( Argument, ArgumentOrderError, CoercionError, InvalidCommandError, MissingArgumentError, MixedArgumentError, Parameter, Token, ValidationError, ) def positive_validator(type_, value): if value <= 0: # Seeing if we can translate a ValueError into a ValidationError as helpfully as possible. raise ValueError("Value must be positive.") def multi_positive_validator(type_, values): for value in values: if value <= 0: raise ValueError("Value must be positive.") def test_exceptions_missing_argument_single(app, console): @app.command def foo(bar: int): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app("foo", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--bar" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_missing_argument_flag(app, console): @app.command def foo(bar: bool): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app("foo", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--bar" flag required. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_validation_error_cli_single_positional(app, console): argument = Argument( hint=int, parameter=Parameter(name=("--bar",), validator=positive_validator), tokens=[ Token(keyword=None, value="-2", source="cli"), ], ) with pytest.raises(ValidationError) as e: argument.convert_and_validate() expected = dedent( """ ValidationError Invalid value "-2" for "BAR". Value must be positive. """ ).strip() assert str(e.value) == expected def test_exceptions_validation_error_cli_single_keyword(app, console): argument = Argument( hint=int, parameter=Parameter(name=("--bar",), validator=positive_validator), tokens=[ Token(keyword="--bar", value="-2", source="cli"), ], ) with pytest.raises(ValidationError) as e: argument.convert_and_validate() expected = dedent( """ ValidationError Invalid value "-2" for "--bar". Value must be positive. """ ).strip() assert str(e.value) == expected def test_exceptions_validation_error_non_cli_single_keyword(app, console): argument = Argument( hint=int, parameter=Parameter(name=("--bar",), validator=positive_validator), tokens=[ Token(value="-2", source="test"), ], ) with pytest.raises(ValidationError) as e: argument.convert_and_validate() expected = dedent( """ ValidationError Invalid value "-2" for "BAR" provided by "test". Value must be positive. """ ).strip() assert str(e.value) == expected def test_exceptions_validation_error_cli_multi_positional(app, console): argument = Argument( hint=tuple[int, int], parameter=Parameter(name=("--bar",), validator=multi_positive_validator), tokens=[ Token(keyword=None, value="100", source="cli"), Token(keyword=None, value="-2", source="cli"), ], ) with pytest.raises(ValidationError) as e: argument.convert_and_validate() expected = dedent( """ ValidationError Invalid value "(100, -2)" for "BAR". Value must be positive. """ ).strip() assert str(e.value) == expected def test_exceptions_validation_error_cli_multi_keyword(app, console): argument = Argument( hint=tuple[int, int], parameter=Parameter(name=("--bar",), validator=multi_positive_validator), tokens=[ Token(keyword="--bar", value="100", source="cli"), Token(keyword="--bar", value="-2", source="cli"), ], ) with pytest.raises(ValidationError) as e: argument.convert_and_validate() expected = dedent( """ ValidationError Invalid value "(100, -2)" for "--bar". Value must be positive. """ ).strip() assert str(e.value) == expected def test_exceptions_coercion_error_from_positional_cli(app, console): @app.command def foo(bar: int): pass with console.capture() as capture, pytest.raises(CoercionError): app("foo fizz", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid value for "BAR": unable to convert "fizz" into int. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_coercion_error_from_keyword_cli(app, console): @app.command def foo(bar: Annotated[int, Parameter(name=("--bar", "-b"))]): pass with console.capture() as capture, pytest.raises(CoercionError): app("foo -b fizz", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid value for "-b": unable to convert "fizz" into int. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_coercion_error_verbose(app, console): @app.command def foo(bar: int): pass with console.capture() as capture, pytest.raises(CoercionError): app("foo fizz", console=console, exit_on_error=False, verbose=True) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ CoercionError │ """ ) assert actual.startswith(expected) expected = dedent( """\ │ foo(bar: int) │ │ Root Input Tokens: ['foo', 'fizz'] │ │ Invalid value for "BAR": unable to convert "fizz" into int. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual.endswith(expected) def test_exceptions_mixed_argument_error(app, console): @app.default def foo(bar: Union[int, dict]): pass with console.capture() as capture, pytest.raises(MixedArgumentError): app("--bar 5 --bar.baz fizz", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Cannot supply keyword & non-keyword arguments to "--bar". │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_unknown_command(app, console): @app.command def foo(bar: int): pass with console.capture() as capture, pytest.raises(InvalidCommandError): app("bar fizz", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Unknown command "bar". │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_argument_order_error_singular(app, console): @app.command def foo(a, b, c): pass with console.capture() as capture, pytest.raises(ArgumentOrderError): app("foo --b=5 1 2", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Cannot specify token '2' positionally for parameter 'c' due to │ │ previously specified keyword '--b'. '--b' must either be passed │ │ positionally, or '2' must be passed as a keyword to '--c'. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_exceptions_argument_order_error_plural(app, console): @app.command def foo(a, b, c): pass with console.capture() as capture, pytest.raises(ArgumentOrderError): app("foo --a=1 --b=5 3", console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Cannot specify token '3' positionally for parameter 'c' due to │ │ previously specified keywords ['--a', '--b']. ['--a', '--b'] must │ │ either be passed positionally, or '3' must be passed as a keyword │ │ to '--c'. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected cyclopts-3.9.0/tests/test_group.py000066400000000000000000000111571475451620500173100ustar00rootroot00000000000000import itertools from typing import Annotated, Sequence from unittest.mock import Mock import pytest import cyclopts.group from cyclopts import App, Group, Parameter, Token from cyclopts.exceptions import ValidationError from cyclopts.group import sort_groups def upper(type_, tokens: Sequence[Token]): return tokens[0].value.upper() def test_group_show_property(): assert Group().show is False assert Group("Foo").show is True assert Group("Foo", show=False).show is False def test_group_default_parameter_converter(app, assert_parse_args): food_group = Group("Food", default_parameter=Parameter(converter=upper)) @app.default def foo(ice_cream: Annotated[str, Parameter(group=food_group)]): pass assert_parse_args(foo, "chocolate", "CHOCOLATE") def test_command_validator(app, assert_parse_args): def bar_must_be_1(bar): if bar == 1: return raise ValueError @app.command(validator=bar_must_be_1) def foo(bar: int): pass assert_parse_args(foo, "foo 1", bar=1) with pytest.raises(ValidationError) as e: app("foo 2", exit_on_error=False) assert str(e.value) == "Invalid values for command 'foo'." def test_command_validator_with_message(app, assert_parse_args): def bar_must_be_1(bar): if bar == 1: return raise ValueError("The value 'bar' must be 1.") @app.command(validator=bar_must_be_1) def foo(bar: int): pass assert_parse_args(foo, "foo 1", bar=1) with pytest.raises(ValidationError) as e: app("foo 2", exit_on_error=False) assert str(e.value) == "Invalid values for command 'foo'. The value 'bar' must be 1." def test_group_command_default_parameter_resolution(app): app_validator = Mock() sub_app_validator = Mock() command_validator = Mock() command_group_validator = Mock() app.validator = app_validator app.command(bar := App("bar", validator=sub_app_validator)) @bar.command(validator=command_validator, group=Group("Test", validator=command_group_validator)) def cmd(foo=5): return 100 assert 100 == app("bar cmd") app_validator.assert_not_called() command_group_validator.assert_called_once() sub_app_validator.assert_not_called() command_validator.assert_called_once() def test_group_default_parameter_validator(app): validator = Mock() food_group = Group("Food", default_parameter=Parameter(validator=validator)) @app.default def foo(ice_cream: Annotated[str, Parameter(group=food_group)]): pass app.parse_args("chocolate") validator.assert_called_once() def test_group_validator(app): validator = Mock() group = Group("Spices", validator=validator) @app.default def foo( salt: Annotated[bool, Parameter("--rock-salt", group=group)] = False, pepper: Annotated[bool, Parameter("--peppercorn", group=group)] = False, ketchup: bool = False, ): pass app.parse_args("--rock-salt --peppercorn --ketchup") validator.assert_called_once() provided_arguments = validator.call_args_list[0][0][0] assert len(provided_arguments) == 2 assert provided_arguments[0].name == "--rock-salt" assert provided_arguments[1].name == "--peppercorn" def test_group_sort_key_property(): assert Group().sort_key is None assert Group()._sort_key is cyclopts.group.UNSET g = Group(sort_key=1) assert g.sort_key == 1 @pytest.fixture def mock_sort_key_counter(mocker): mock = mocker.patch("cyclopts.group._sort_key_counter") mock.__next__.side_effect = itertools.count() return mock def test_group_sorted_classmethod_basic(mock_sort_key_counter): g4 = Group("unsorted group") g1 = Group.create_ordered("foo") g2 = Group.create_ordered("bar") g3 = Group.create_ordered("baz", sort_key="non-int value") assert g1.sort_key == (cyclopts.group.UNSET, 0) assert g2.sort_key == (cyclopts.group.UNSET, 1) assert g3.sort_key == ("non-int value", 2) assert g4.sort_key is None res = sort_groups([g1, g2, g3, g4], ["a", "b", "c", "d"]) assert ([g3, g1, g2, g4], ["c", "a", "b", "d"]) == res def test_group_sorted_classmethod_tuple(mock_sort_key_counter): g1 = Group.create_ordered("foo1", sort_key=("tuple", 7)) g2 = Group.create_ordered("foo2", sort_key=lambda x: ("tuple", 5)) def f_tuple_str(x): return "tuple" g3 = Group.create_ordered("foo3", sort_key=(f_tuple_str, 3)) g4 = Group.create_ordered("foo4", sort_key=("tuple", 3)) res = sort_groups([g1, g2, g3, g4], ["a", "b", "c", "d"]) assert ([g3, g4, g2, g1], ["c", "d", "b", "a"]) == res cyclopts-3.9.0/tests/test_group_extractors.py000066400000000000000000000023501475451620500215610ustar00rootroot00000000000000import pytest from cyclopts import App, Group, Parameter from cyclopts.group_extractors import groups_from_app def test_groups_annotated_invalid_recursive_definition(): """A default_parameter isn't allowed to have a group set, as it would introduce a paradox.""" default_parameter = Parameter(group="Drink") # pyright: ignore[reportGeneralTypeIssues] with pytest.raises(ValueError): Group("Food", default_parameter=default_parameter) def test_groups_from_app_implicit(): def validator(argument_collection): pass app = App(help_flags=[], version_flags=[]) @app.command(group="Food") def food1(): pass @app.command(group=Group("Food", validator=validator)) def food2(): pass @app.command(group="Drink") def drink1(): pass actual_groups = groups_from_app(app) assert actual_groups == [ (Group("Drink"), [app["drink1"]]), (Group("Food", validator=validator), [app["food1"], app["food2"]]), ] def test_commands_groups_name_collision(app): @app.command(group=Group("Foo")) def foo(): pass @app.command(group=Group("Foo")) def bar(): pass with pytest.raises(ValueError): groups_from_app(app) cyclopts-3.9.0/tests/test_help.py000066400000000000000000002336611475451620500171120ustar00rootroot00000000000000import sys from enum import Enum from textwrap import dedent from typing import Annotated, List, Literal, Optional, Sequence, Set, Tuple, Union import pytest from cyclopts import App, Group, Parameter from cyclopts.argument import ArgumentCollection from cyclopts.exceptions import CoercionError, MissingArgumentError from cyclopts.help import ( HelpPanel, create_parameter_help_panel, format_command_entries, format_usage, ) @pytest.fixture def app(): return App( name="app", help="App Help String Line 1.", ) def test_empty_help_panel_rich_silent(console): help_panel = HelpPanel(format="command", title="test") with console.capture() as capture: console.print(help_panel) actual = capture.get() assert actual == "" def test_help_mutable_default(app): """Ensures it doesn't crash; see issue #215.""" @app.default def main(users: List[str] = ["a", "b"]) -> None: # noqa: B006 print(users) app(["--help"]) def test_help_default_action(app, console): """No command should default to help.""" with console.capture() as capture: app([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_custom_usage(app, console): app.usage = "My custom usage." with console.capture() as capture: app([], console=console) actual = capture.get() expected = dedent( """\ My custom usage. App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_custom_usage_subapp(app, console): app.command(App(name="foo", usage="My custom usage.")) with console.capture() as capture: app(["foo", "--help"], console=console) actual = capture.get() expected = dedent( """\ My custom usage. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_default_help_flags(console): """Standard help flags.""" app = App(name="app", help="App Help String Line 1.") with console.capture() as capture: app(["--help"], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_usage_empty(console): app = App( name="app", help="App Help String Line 1.", help_flags=[], version_flags=[], ) with console.capture() as capture: console.print(format_usage(app, [])) actual = capture.get() assert actual == "Usage: app\n\n" def test_help_format_usage_command(app, console): @app.command def foo(): pass with console.capture() as capture: console.print(format_usage(app, [])) actual = capture.get() assert actual == "Usage: app COMMAND\n\n" def test_format_commands_docstring(app, console): @app.command def foo(): """Docstring for foo. This should not be shown. """ panel = HelpPanel(title="Commands", format="command") panel.entries.extend(format_command_entries((app["foo"],), format="restructuredtext")) with console.capture() as capture: console.print(panel) actual = capture.get() assert actual == ( "╭─ Commands ─────────────────────────────────────────────────────────╮\n" "│ foo Docstring for foo. │\n" "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_format_commands_docstring_long_only(app, console): """ PEP-0257 says that the short_description and long_description should be separated by an empty newline. We hijack the docstring parsing a little bit to enforce this. See https://github.com/BrianPugh/cyclopts/issues/74 """ @app.command def foo(): """ This function doesn't have a short description. This is a continuation of the long description. """ # noqa: D404 panel = HelpPanel(title="Commands", format="command") panel.entries.extend(format_command_entries((app["foo"],), format="restructuredtext")) with console.capture() as capture: console.print(panel) actual = capture.get() assert actual == ( "╭─ Commands ─────────────────────────────────────────────────────────╮\n" "│ foo │\n" "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_format_commands_no_show(app, console, assert_parse_args): @app.command def foo(): """Docstring for foo.""" pass @app.command(show=False) def bar(): """Should not be shown.""" pass assert_parse_args(foo, "foo") assert_parse_args(bar, "bar") with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo Docstring for foo. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_format_commands_explicit_help(app, console): @app.command(help="Docstring for foo.") def foo(): """Should not be shown.""" pass panel = HelpPanel(title="Commands", format="command") panel.entries.extend(format_command_entries((app["foo"],), format="restructuredtext")) with console.capture() as capture: console.print(panel) actual = capture.get() assert actual == ( "╭─ Commands ─────────────────────────────────────────────────────────╮\n" "│ foo Docstring for foo. │\n" "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_format_commands_explicit_name(app, console): @app.command(name="bar") def foo(): """Docstring for bar. This should not be shown. """ pass panel = HelpPanel(title="Commands", format="command") panel.entries.extend(format_command_entries((app["bar"],), format="restructuredtext")) with console.capture() as capture: console.print(panel) actual = capture.get() assert actual == ( "╭─ Commands ─────────────────────────────────────────────────────────╮\n" "│ bar Docstring for bar. │\n" "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_help_empty(console): app = App(name="foo", version_flags=[], help_flags=[]) with console.capture() as capture: app.help_print(console=console) actual = capture.get() assert actual == "Usage: foo\n\n" def test_format_choices_rich_format(app, console, assert_parse_args): app.help_format = "rich" @app.default def foo(region: Literal["us", "ca"]): """Docstring for foo.""" pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND [ARGS] [OPTIONS] App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * REGION --region [choices: us, ca] [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.fixture def capture_format_group_parameters(console, default_function_groups): def inner(cmd): argument_collection = ArgumentCollection._from_callable( cmd, None, parse_docstring=True, ) with console.capture() as capture: group = argument_collection.groups[0] group_argument_collection = argument_collection.filter_by(group=group) console.print(create_parameter_help_panel(group, group_argument_collection, "restructuredtext")) return capture.get() return inner def test_help_format_group_parameters_empty(capture_format_group_parameters): def cmd( foo: Annotated[str, Parameter(show=False)], ): pass actual = capture_format_group_parameters(cmd) expected = "" assert actual == expected def test_help_format_group_parameters(capture_format_group_parameters): def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")], bar: Annotated[str, Parameter(help="Docstring for bar.")], ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo Docstring for foo. [required] │ │ * BAR --bar Docstring for bar. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_short_name(capture_format_group_parameters): def cmd( foo: Annotated[str, Parameter(name=["--foo", "-f"], help="Docstring for foo.")], ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo -f Docstring for foo. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_from_docstring(capture_format_group_parameters): def cmd(foo: str, bar: str): """ Parameters ---------- foo: str Docstring for foo. bar: str Docstring for bar. """ pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo Docstring for foo. [required] │ │ * BAR --bar Docstring for bar. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_bool_flag(capture_format_group_parameters): def cmd( foo: Annotated[bool, Parameter(help="Docstring for foo.")] = True, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo --no-foo Docstring for foo. [default: True] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.mark.parametrize("negative_str", ["--yesnt-foo", "yesnt-foo"]) def test_help_format_group_parameters_bool_flag_custom_negative(capture_format_group_parameters, negative_str): def cmd( foo: Annotated[bool, Parameter(negative=negative_str, help="Docstring for foo.")] = True, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo --yesnt-foo Docstring for foo. [default: True] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_list_flag(capture_format_group_parameters): def cmd( foo: Annotated[Optional[List[int]], Parameter(help="Docstring for foo.")] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo --empty-foo Docstring for foo. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_defaults(capture_format_group_parameters): def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")] = "fizz", bar: Annotated[str, Parameter(help="Docstring for bar.")] = "buzz", ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [default: fizz] │ │ BAR --bar Docstring for bar. [default: buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_defaults_no_show(capture_format_group_parameters): def cmd( foo: Annotated[str, Parameter(show_default=False, help="Docstring for foo.")] = "fizz", bar: Annotated[str, Parameter(help="Docstring for bar.")] = "buzz", ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. │ │ BAR --bar Docstring for bar. [default: buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_literal_no_show(capture_format_group_parameters): def cmd( foo: Annotated[Literal["fizz", "buzz"], Parameter(show_choices=False, help="Docstring for foo.")] = "fizz", bar: Annotated[Literal["fizz", "buzz"], Parameter(help="Docstring for bar.")] = "buzz", ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [default: fizz] │ │ BAR --bar Docstring for bar. [choices: fizz, buzz] [default: │ │ buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_literal_union(capture_format_group_parameters): def cmd( foo: Annotated[ Union[int, Literal["fizz", "buzz"], Literal["bar"]], Parameter(help="Docstring for foo.") ] = "fizz", ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: fizz, buzz, bar] [default: │ │ fizz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.mark.skipif(sys.version_info < (3, 10), reason="Pipe Typing Syntax") def test_help_format_group_parameters_choices_literal_union_python310_syntax_0(capture_format_group_parameters): def cmd( foo: Annotated[ Literal["fizz", "buzz"] | Literal["bar"], Parameter(help="Docstring for foo.") # pyright: ignore ] = "fizz", ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: fizz, buzz, bar] [default: │ │ fizz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected @pytest.mark.skipif(sys.version_info < (3, 10), reason="Pipe Typing Syntax") def test_help_format_group_parameters_choices_literal_union_python310_syntax_1(capture_format_group_parameters): def cmd(foo: Literal["fizz", "buzz"] | Literal["bar"] = "fizz"): # pyright: ignore pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo [choices: fizz, buzz, bar] [default: fizz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_enum(capture_format_group_parameters): class CompSciProblem(Enum): fizz = "bleep bloop blop" buzz = "blop bleep bloop" def cmd( foo: Annotated[CompSciProblem, Parameter(help="Docstring for foo.")] = CompSciProblem.fizz, bar: Annotated[CompSciProblem, Parameter(help="Docstring for bar.")] = CompSciProblem.buzz, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: fizz, buzz] [default: │ │ fizz] │ │ BAR --bar Docstring for bar. [choices: fizz, buzz] [default: │ │ buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_enum_list(capture_format_group_parameters): class CompSciProblem(Enum): fizz = "bleep bloop blop" buzz = "blop bleep bloop" def cmd( foo: Annotated[ Optional[list[CompSciProblem]], # pyright: ignore Parameter(help="Docstring for foo.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: fizz, buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_enum_list_typing(capture_format_group_parameters): class CompSciProblem(Enum): fizz = "bleep bloop blop" buzz = "blop bleep bloop" def cmd( foo: Annotated[ Optional[List[CompSciProblem]], Parameter(help="Docstring for foo.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: fizz, buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_enum_sequence(capture_format_group_parameters): class CompSciProblem(Enum): fizz = "bleep bloop blop" buzz = "blop bleep bloop" def cmd( foo: Annotated[ Optional[Sequence[CompSciProblem]], # pyright: ignore Parameter(help="Docstring for foo.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: fizz, buzz] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_literal_sequence(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ Optional[Sequence[Literal["build", "deploy"]]], # pyright: ignore Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) print(expected) assert actual == expected def test_help_format_group_parameters_choices_literal_set(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ Optional[set[Literal["build", "deploy"]]], # pyright: ignore Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) print(expected) assert actual == expected @pytest.mark.skipif( sys.version_info < (3, 10), reason="https://peps.python.org/pep-0563/ Postponed Evaluation of Annotations" ) def test_help_parameter_string_annotation(capture_format_group_parameters): def cmd(number: "Annotated[int,Parameter(name=['--number','-n'])]"): """Print number. Args: number (int): A number to print. """ pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * NUMBER --number -n A number to print. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) print(actual) print(expected) assert actual == expected def test_help_format_group_parameters_choices_literal_set_typing(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ Optional[Set[Literal["build", "deploy"]]], Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_literal_tuple(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ Optional[tuple[Literal["build", "deploy"]]], # pyright: ignore Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), ] = None, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) print(actual) assert actual == expected def test_help_format_group_parameters_choices_literal_tuple_typing(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ Tuple[Literal["build", "deploy"]], Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_choices=True), ] = ("build",), ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] [default: ('build',)] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_literal_tuple_variadic_typing(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ Tuple[Literal["build", "deploy"], ...], Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_choices=True), ] = (), ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] [default: ()] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_format_group_parameters_choices_literal_tuple_variadic(capture_format_group_parameters): def cmd( steps_to_skip: Annotated[ tuple[Literal["build", "deploy"], ...], # pyright: ignore Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_choices=True), ] = ("build",), ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ STEPS-TO-SKIP Docstring for steps_to_skip. [choices: build, │ │ --steps-to-skip deploy] [default: ('build',)] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) print(actual) assert actual == expected def test_help_format_group_parameters_env_var(capture_format_group_parameters): def cmd( foo: Annotated[int, Parameter(env_var=["FOO", "BAR"], help="Docstring for foo.")] = 123, ): pass actual = capture_format_group_parameters(cmd) expected = dedent( """\ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [env var: FOO, BAR] [default: 123] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_function(app, console): @app.command(help="Cmd help string.") def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")], *, bar: Annotated[str, Parameter(help="Docstring for bar.")], ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app cmd [ARGS] [OPTIONS] Cmd help string. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo Docstring for foo. [required] │ │ * --bar Docstring for bar. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_parameter_required(app, console): """ Notes ----- * The default value should not show up in the help-page. """ @app.command(help="Cmd help string.") def cmd( foo: Annotated[str, Parameter(required=False, help="Docstring for foo.")], *, bar: Annotated[str, Parameter(required=True, help="Docstring for bar.")] = "some-default-value", ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app cmd [ARGS] [OPTIONS] Cmd help string. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. │ │ * --bar Docstring for bar. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected with pytest.raises(MissingArgumentError): app.parse_args("cmd value1", exit_on_error=False) def test_help_print_function_defaults(app, console): @app.command(help="Cmd help string.") def cmd( *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], bar: Annotated[str, Parameter(help="Docstring for bar.")] = "bar-value", baz: Annotated[str, Parameter(help="Docstring for bar.", env_var="BAZ")] = "baz-value", ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app cmd [ARGS] [OPTIONS] Cmd help string. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ --bar Docstring for bar. [default: bar-value] │ │ --baz Docstring for bar. [env var: BAZ] [default: baz-value] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_function_no_parse(app, console): @app.command(help="Cmd help string.") def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")], *, bar: Annotated[str, Parameter(parse=False)], ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app cmd [ARGS] [OPTIONS] Cmd help string. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo Docstring for foo. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_parameter_group_description(app, console): @app.command(group_parameters=Group("Custom Title", help="Parameter description.")) def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")], *, bar: Annotated[str, Parameter(parse=False)], ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app cmd [ARGS] [OPTIONS] ╭─ Custom Title ─────────────────────────────────────────────────────╮ │ Parameter description. │ │ │ │ * FOO --foo Docstring for foo. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_parameter_group_no_show(app, console): no_show_group = Group("Custom Title", help="Parameter description.", show=False) @app.command def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")], bar: Annotated[str, Parameter(help="Docstring for foo.", group=no_show_group)], ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app cmd [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo Docstring for foo. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_command_group_description(app, console): @app.command(group=Group("Custom Title", help="Command description.")) def cmd( foo: Annotated[str, Parameter(help="Docstring for foo.")], *, bar: Annotated[str, Parameter(parse=False)], ): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Custom Title ─────────────────────────────────────────────────────╮ │ Command description. │ │ │ │ cmd │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_command_group_no_show(app, console): no_show_group = Group("Custom Title", show=False) @app.command(group=no_show_group) def cmd1(): pass @app.command() def cmd2(): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ cmd2 │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_combined_parameter_command_group(app, console): group = Group("Custom Title") app["--help"].group = group @app.default def default(value1: Annotated[int, Parameter(group=group)]): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND [ARGS] [OPTIONS] App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Custom Title ─────────────────────────────────────────────────────╮ │ * VALUE1 --value1 [required] │ │ --help -h Display this message and exit. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands(app, console): @app.command(help="Cmd1 help string.") def cmd1(): pass @app.command(help="Cmd2 help string.") def cmd2(): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ cmd1 Cmd1 help string. │ │ cmd2 Cmd2 help string. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands_group_sort_key(app, console): @app.command(group=Group("4", sort_key=5)) def cmd1(): pass @app.command(group=Group("3", sort_key=lambda x: 10)) def cmd2(): pass @app.command(group=Group("2", sort_key=lambda x: None)) def cmd3(): pass @app.command(group=Group("1")) def cmd4(): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ 4 ────────────────────────────────────────────────────────────────╮ │ cmd1 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ 3 ────────────────────────────────────────────────────────────────╮ │ cmd2 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ 1 ────────────────────────────────────────────────────────────────╮ │ cmd4 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ 2 ────────────────────────────────────────────────────────────────╮ │ cmd3 │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands_and_function(app, console): @app.command(help="Cmd1 help string.") def cmd1(): pass @app.command(help="Cmd2 help string.") def cmd2(): pass @app.default() def default( foo: Annotated[str, Parameter(help="Docstring for foo.")], *, bar: Annotated[str, Parameter(help="Docstring for bar.")], ): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND [ARGS] [OPTIONS] App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ cmd1 Cmd1 help string. │ │ cmd2 Cmd2 help string. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * FOO --foo Docstring for foo. [required] │ │ * --bar Docstring for bar. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands_special_flag_reassign(app, console): app["--help"].group = "Admin" with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Admin ────────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_parameters_no_negative_from_default_parameter(app, console): app.default_parameter = Parameter(negative=()) @app.command def foo(*, flag: bool): pass with console.capture() as capture: app.help_print(["foo"], console=console) actual = capture.get() expected = dedent( """\ Usage: app foo [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * --flag [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands_plus_meta(console): app = App( name="app", help_flags=[], version_flags=[], help="App Help String Line 1.", ) app.meta.group_arguments = "Session Arguments" app.meta.group_parameters = "Session Parameters" @app.command(help="Cmd1 help string.") def cmd1(): pass @app.meta.command(help="Meta cmd help string.") def meta_cmd(): pass @app.command(help="Cmd2 help string.") def cmd2(): pass @app.meta.default def main( *tokens: Annotated[str, Parameter(show=False)], hostname: Annotated[str, Parameter(help="Hostname to connect to.")], ): pass app.meta.help_flags = "--help" app.meta["--help"].group = "Admin" with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Admin ────────────────────────────────────────────────────────────╮ │ --help Display this message and exit. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Commands ─────────────────────────────────────────────────────────╮ │ cmd1 Cmd1 help string. │ │ cmd2 Cmd2 help string. │ │ meta-cmd Meta cmd help string. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Session Parameters ───────────────────────────────────────────────╮ │ * --hostname Hostname to connect to. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands_sort_key(app, console): @app.command # No user-specified sort_key; will go LAST (but before commands starting with --). def alice(): pass @app.command(sort_key=2) def bob(): pass @app.command(sort_key=1) # Since 1 < 2 (from bob), should go first def charlie(): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ charlie │ │ bob │ │ alice │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_print_commands_plus_meta_short(app, console): app.help = None @app.command(help="Cmd1 help string.") def cmd1(): pass app.meta.group_arguments = "Session Arguments" app.meta.group_parameters = "Session Parameters" @app.meta.command(help="Meta cmd help string.") def meta_cmd(a: int): """ Parameters ---------- a Some value. """ pass # Otherwise it will use the default meta app["meta-cmd"].group_parameters = app.group_parameters @app.command(help="Cmd2 help string.") def cmd2(): pass @app.meta.default def main( *tokens: str, hostname: Annotated[str, Parameter(name=["--hostname", "-n"], help="Hostname to connect to.")], ): """App Help String Line 1 from meta.""" pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND App Help String Line 1 from meta. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ cmd1 Cmd1 help string. │ │ cmd2 Cmd2 help string. │ │ meta-cmd Meta cmd help string. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Session Arguments ────────────────────────────────────────────────╮ │ TOKENS │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Session Parameters ───────────────────────────────────────────────╮ │ * --hostname -n Hostname to connect to. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected # Add in a default root app. @app.default def root_default_cmd(rdp): """Root Default Command Short Description. Parameters ---------- rdp: RDP description. """ pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: app COMMAND [ARGS] [OPTIONS] Root Default Command Short Description. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ cmd1 Cmd1 help string. │ │ cmd2 Cmd2 help string. │ │ meta-cmd Meta cmd help string. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * RDP --rdp RDP description. [required] │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Session Arguments ────────────────────────────────────────────────╮ │ TOKENS │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Session Parameters ───────────────────────────────────────────────╮ │ * --hostname -n Hostname to connect to. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected # Test that the meta command help parsing is correct. with console.capture() as capture: app.help_print(["meta-cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: app meta-cmd [ARGS] [OPTIONS] Meta cmd help string. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * A --a Some value. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_restructuredtext(app, console): description = dedent( """\ This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. `This is a hyperlink. `_ The following are bulletpoints: * bulletpoint 1 * bulletpoint 2 """ ) app = App(help=description, help_format="rst") @app.command def foo(bar): """This is **bold**.""" with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: test_help COMMAND This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. This is a hyperlink. The following are bulletpoints: • bulletpoint 1 • bulletpoint 2 ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo This is bold. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) # Rich sticks a bunch of trailing spaces on lines. expected = "\n".join(x.strip() for x in expected.split("\n")) actual = "\n".join(x.strip() for x in actual.split("\n")) assert actual == expected def test_help_markdown(app, console): description = dedent( """\ This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. [This is a hyperlink.](https://cyclopts.readthedocs.io) The following are bulletpoints: * bulletpoint 1 * bulletpoint 2 """ ) app = App(help=description, help_format="markdown") @app.command def foo(bar): """This is **bold**.""" with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: test_help COMMAND This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. This is a hyperlink. The following are bulletpoints: • bulletpoint 1 • bulletpoint 2 ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo This is bold. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) # Rich sticks a bunch of trailing spaces on lines. expected = "\n".join(x.strip() for x in expected.split("\n")) actual = "\n".join(x.strip() for x in actual.split("\n")) assert actual == expected def test_help_rich(app, console): """Newlines actually get interpreted with rich.""" description = dedent( """\ This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. [red]This text is red.[/red] """ ) app = App(help=description, help_format="rich") @app.command def foo(bar): """This is [italic]italic[/italic].""" with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: test_help COMMAND This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. This text is red. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo This is italic. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_plaintext(app, console): """Tests that plaintext documents don't get interpreted otherwise.""" description = dedent( """\ This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. [red]This text is red.[/red] These are bulletpoints: * point 1 * point 2 """ ) app = App(help=description, help_format="plaintext") @app.command def foo(bar): """This is [italic]italic[/italic].""" with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: test_help COMMAND This is a long sentence that is spread across three lines. This is a new paragraph. This is another sentence of that paragraph. [red]This text is red.[/red] These are bulletpoints: * point 1 * point 2 ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo This is [italic]italic[/italic]. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_help_consistent_formatting(app, console): """Checks to make sure short-descriptions and full-descriptions are rendered using the same formatter. https://github.com/BrianPugh/cyclopts/issues/113 """ app.help_format = "markdown" @app.command def cmd(): """[bold]Short description[/bold].""" with console.capture() as capture: app.help_print([], console=console) actual_help = capture.get() # Hack to extract out the short_description from the help-page actual_help = next(x for x in actual_help.split("\n") if "Short description" in x) actual_help = actual_help[5:-1].strip() with console.capture() as capture: app.help_print(["cmd"], console=console) # Hack to extract out the short_description from the help-page actual_cmd_help = capture.get() actual_cmd_help = next(x for x in actual_cmd_help.split("\n") if "Short description" in x) actual_cmd_help = actual_cmd_help.strip() assert actual_help == actual_cmd_help def test_help_help_on_error(app, console): app.help = "This is the App's Help." app.help_on_error = True @app.command def foo(count: int): """This is Foo's Help.""" pass with console.capture() as capture, pytest.raises(CoercionError): app(["foo", "bar"], console=console, exit_on_error=False) actual = capture.get() expected = dedent( """\ Usage: app foo [ARGS] [OPTIONS] This is Foo's Help. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * COUNT --count [required] │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Invalid value for "COUNT": unable to convert "bar" into int. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected cyclopts-3.9.0/tests/test_interactive_shell.py000066400000000000000000000022661475451620500216610ustar00rootroot00000000000000def test_interactive_shell(app, mocker, console): mocker.patch( "cyclopts.core.input", side_effect=[ "foo 1 2 3", "bad-command 123", "", "bar bloop", "quit", ], ) foo_called, bar_called = 0, 0 @app.command def foo(a: int, b: int, c: int): nonlocal foo_called foo_called += 1 @app.command def bar(token): nonlocal bar_called bar_called += 1 with console.capture() as capture: app.interactive_shell(console=console) actual = capture.get() assert foo_called == 1 assert bar_called == 1 assert actual == ( "╭─ Error ────────────────────────────────────────────────────────────╮\n" '│ Unknown command "bad-command". │\n' "╰────────────────────────────────────────────────────────────────────╯\n" ) cyclopts-3.9.0/tests/test_json_string_dict.py000066400000000000000000000024341475451620500215140ustar00rootroot00000000000000import json from dataclasses import dataclass, field from typing import Annotated, Dict, Optional import pytest from cyclopts import Parameter @dataclass class User: id: int name: str = "John Doe" tastes: Dict[str, int] = field(default_factory=dict) def test_bind_dataclass_from_env_json(app, assert_parse_args, monkeypatch): @app.command def foo(some_number: int, user: Annotated[User, Parameter(env_var="USER")]): pass external_data = { "id": 123, # "name" is purposely missing. "tastes": { "wine": 9, "cheese": 7, "cabbage": 1, }, } monkeypatch.setenv("USER", json.dumps(external_data)) assert_parse_args( foo, "foo 100", 100, User(**external_data), ) @pytest.mark.parametrize( "cmd_str", [ """--origin='{"x": 1, "y": 2}'""", """--origin '{"x": 1, "y": 2}'""", """--origin='{"x": 1, "y": 2, "label": null}'""", ], ) def test_bind_dataclass_from_cli_json(app, assert_parse_args, cmd_str): @dataclass class Coordinate: x: int y: int label: Optional[str] = None @app.default def main(origin: Coordinate): pass assert_parse_args(main, cmd_str, Coordinate(1, 2)) cyclopts-3.9.0/tests/test_json_string_list.py000066400000000000000000000055541475451620500215520ustar00rootroot00000000000000from dataclasses import dataclass from typing import Annotated, Iterable, Optional, Sequence import pytest from cyclopts import Parameter LIST_STR_LIKE_TYPES = [list, list[str], Sequence, Sequence[str], Iterable, Iterable[str]] @dataclass class User: name: str age: int @pytest.mark.parametrize( "cmd_str", [ "--values=[1,2,3]", "--values [1,2,3]", "--values [1,2] --values [3]", "--values [1] --values '[2, 3]'", "--values 1 --values [2,3]", "--values [1] --values [2] --values [3]", ], ) @pytest.mark.parametrize("json_list", [None, True]) def test_json_list_cli_str(app, assert_parse_args, cmd_str, json_list): @app.default def main(values: Annotated[list[int], Parameter(json_list=json_list)]): pass assert_parse_args(main, cmd_str, [1, 2, 3]) @pytest.mark.parametrize("annotation", LIST_STR_LIKE_TYPES) def test_json_list_str_none(app, assert_parse_args, annotation): """A ``list`` or ``list[str]`` annotation should **not** be set-able via json-string by default. May change in v4. """ @app.default def main(values: annotation): # pyright: ignore pass assert_parse_args(main, ['["foo", "bar"]'], ['["foo", "bar"]']) def test_json_list_optional_int(app, assert_parse_args): @app.default def main(values: list[Optional[int]]): # pyright: ignore pass assert_parse_args(main, ["[1, null, 2]"], [1, None, 2]) @pytest.mark.parametrize("annotation", LIST_STR_LIKE_TYPES) def test_json_list_str_cli_str_true(app, assert_parse_args, annotation): @app.default def main(values: Annotated[annotation, Parameter(json_list=True)]): # pyright: ignore pass assert_parse_args(main, ['["foo", "bar"]'], ["foo", "bar"]) @pytest.mark.parametrize("annotation", [list, list[str]]) def test_json_list_str_cli_str_false(app, assert_parse_args, annotation): @app.default def main(values: Annotated[annotation, Parameter(json_list=False)]): # pyright: ignore pass assert_parse_args(main, ['["foo", "bar"]'], ['["foo", "bar"]']) @pytest.mark.parametrize( "env_str", [ "[1,2,3]", "[1, 2, 3]", ], ) @pytest.mark.parametrize("json_list", [None, True]) def test_json_list_env_str(app, assert_parse_args, env_str, monkeypatch, json_list): monkeypatch.setenv("VALUES", env_str) @app.default def main(values: Annotated[list[int], Parameter(env_var="VALUES", json_list=json_list)]): pass assert_parse_args(main, "", [1, 2, 3]) @pytest.mark.skip(reason="Need to implement token exploding.") def test_json_list_of_dataclass_cli(app, assert_parse_args): @app.default def main(values: list[User]): pass assert_parse_args( main, ["--values", '[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 40}]'], [User("Alice", 30), User("Bob", 40)], ) cyclopts-3.9.0/tests/test_meta.py000066400000000000000000000115531475451620500171020ustar00rootroot00000000000000from textwrap import dedent from typing import Annotated import pytest from cyclopts import App, Parameter @pytest.mark.parametrize( "cmd_str", [ "1 --b 2 --c=c-value-manual --meta-flag", "1 --b=2 --c=c-value-manual --meta-flag", "1 --b=2 --c c-value-manual --meta-flag", ], ) def test_meta_basic(app, cmd_str): @app.default def foo(a: int, b: int, c="c-value"): assert a == 1 assert b == 2 assert c == "c-value-manual" @app.meta.default def meta(*tokens: Annotated[str, Parameter(allow_leading_hyphen=True)], meta_flag: bool = False): assert meta_flag app(tokens) app.meta(cmd_str) def test_meta_app_config_inheritance(app): app.config = ("foo", "bar") assert app.meta.config == ("foo", "bar") @pytest.fixture def queue(): return [] @pytest.fixture def nested_meta_app(queue, console): subapp = App(console=console) @subapp.meta.default def subapp_meta(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]) -> None: """This is subapp's help.""" queue.append("subapp meta") subapp(tokens) @subapp.command def foo(value: int) -> None: """Subapp foo help string. Parameters ---------- value: int The value a user inputted. """ queue.append(f"subapp foo body {value}") app = App(name="test_app", console=console) app.command(subapp.meta, name="subapp") @app.meta.default def meta(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]): queue.append("root meta") app(tokens) return app def test_meta_app_nested_root_help(nested_meta_app, console, queue): with console.capture() as capture: nested_meta_app.meta(["--help"]) actual = capture.get() expected = dedent( """\ Usage: test_app COMMAND ╭─ Commands ─────────────────────────────────────────────────────────╮ │ subapp This is subapp's help. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected assert not queue def test_meta_app_nested_subapp_help(nested_meta_app, console, queue): with console.capture() as capture: nested_meta_app.meta(["subapp", "--help"]) actual = capture.get() expected = dedent( """\ Usage: test_app subapp COMMAND [ARGS] This is subapp's help. ╭─ Commands ─────────────────────────────────────────────────────────╮ │ foo Subapp foo help string. │ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected assert not queue def test_meta_app_nested_subapp_foo_help(nested_meta_app, console, queue): with console.capture() as capture: nested_meta_app.meta(["subapp", "foo", "--help"]) actual = capture.get() expected = dedent( """\ Usage: test_app subapp foo [ARGS] [OPTIONS] Subapp foo help string. ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * VALUE --value The value a user inputted. [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected assert not queue @pytest.mark.parametrize( "cmd_str,expected", [ ("", ["root meta"]), ("subapp", ["root meta", "subapp meta"]), ("subapp foo 5", ["root meta", "subapp meta", "subapp foo body 5"]), ], ) def test_meta_app_nested_exec(nested_meta_app, queue, cmd_str, expected): nested_meta_app.meta(cmd_str) assert queue == expected cyclopts-3.9.0/tests/test_name_transform.py000066400000000000000000000157471475451620500212000ustar00rootroot00000000000000from dataclasses import dataclass from enum import Enum, auto from textwrap import dedent from typing import Annotated import pytest from cyclopts import App, Parameter, default_name_transform @pytest.mark.parametrize( "before,after", [ ("FOO", "foo"), ("_FOO", "foo"), ("_FOO_", "foo"), ("_F_O_O_", "f-o-o"), ], ) def test_default_name_transform(before, after): assert default_name_transform(before) == after def test_app_name_transform_default(app): @app.command def _F_O_O_(): # noqa: N802 pass assert "f-o-o" in app def test_app_name_transform_custom(app): def name_transform(s: str) -> str: return "my-custom-name-transform" app.name_transform = name_transform @app.command def foo(): pass assert "my-custom-name-transform" in app def test_subapp_name_transform_custom(app): """A subapp with an explicitly set ``name_transform`` should NOT inherit from parent.""" def name_transform_1(s: str) -> str: return "my-custom-name-transform-1" def name_transform_2(s: str) -> str: return "my-custom-name-transform-2" app.name_transform = name_transform_1 app.command(subapp := App(name="bar", name_transform=name_transform_2)) @subapp.command def foo(): pass assert "my-custom-name-transform-2" in subapp def test_subapp_name_transform_custom_inherited(app): """A subapp without an explicitly set ``name_transform`` should inherit it from the first parent.""" def name_transform(s: str) -> str: return "my-custom-name-transform" app.name_transform = name_transform app.command(subapp := App(name="bar")) @subapp.command def foo(): pass assert "my-custom-name-transform" in subapp def test_parameter_name_transform_default(app, assert_parse_args): @app.default def foo(*, b_a_r: int): pass assert_parse_args(foo, "--b-a-r 5", b_a_r=5) def test_parameter_name_transform_custom(app, assert_parse_args): app.default_parameter = Parameter(name_transform=lambda s: s) @app.default def foo(*, b_a_r: int): pass assert_parse_args(foo, "--b_a_r 5", b_a_r=5) @pytest.mark.parametrize("transform", [None, lambda s: s]) def test_parameter_name_transform_kwargs(app, assert_parse_args, transform): """Both custom and non-custom transforms should result in the same kwargs.""" app.default_parameter = Parameter(name_transform=transform) @app.default def foo(**kwargs: int): pass assert_parse_args(foo, "--hy-phen=1 --under_score=2", **{"hy-phen": 1, "under_score": 2}) def test_parameter_name_transform_custom_name_override(app, assert_parse_args): app.default_parameter = Parameter(name_transform=lambda s: s) @app.default def foo(*, b_a_r: Annotated[int, Parameter(name="--buzz")]): pass assert_parse_args(foo, "--buzz 5", b_a_r=5) def test_parameter_name_transform_custom_enum(app, assert_parse_args): """name_transform should also be applied to enum options.""" app.default_parameter = Parameter(name_transform=lambda s: s) class SoftwareEnvironment(Enum): DEV = auto() STAGING = auto() PROD = auto() _PROD_OLD = auto() @app.default def foo(*, b_a_r: SoftwareEnvironment = SoftwareEnvironment.STAGING): pass assert_parse_args(foo, "--b_a_r PROD", b_a_r=SoftwareEnvironment.PROD) def test_parameter_name_transform_help(app, console): app.default_parameter = Parameter(name_transform=lambda s: s) @app.default def foo(*, b_a_r: int): pass with console.capture() as capture: app.help_print([], console=console) actual = capture.get() expected = dedent( """\ Usage: foo COMMAND [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * --b_a_r [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_parameter_name_transform_help_enum(app, console): """name_transform should also be applied to enum options on help page.""" app.default_parameter = Parameter(name_transform=lambda s: s) class CompSciProblem(Enum): FIZZ = "bleep bloop blop" BUZZ = "blop bleep bloop" @app.command def cmd( foo: Annotated[CompSciProblem, Parameter(help="Docstring for foo.")] = CompSciProblem.FIZZ, bar: Annotated[CompSciProblem, Parameter(help="Docstring for bar.")] = CompSciProblem.BUZZ, ): pass with console.capture() as capture: app.help_print(["cmd"], console=console) actual = capture.get() expected = dedent( """\ Usage: test_name_transform cmd [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ FOO --foo Docstring for foo. [choices: FIZZ, BUZZ] [default: │ │ FIZZ] │ │ BAR --bar Docstring for bar. [choices: FIZZ, BUZZ] [default: │ │ BUZZ] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_parameter_name_transform_dataclass(app, assert_parse_args): app.default_parameter = Parameter(name_transform=lambda s: s.upper()) @dataclass class Color: red: int green: int blue: int @dataclass class User: name: str favorite_color: Color @app.default def default(user: Annotated[User, Parameter(name="--user")]): pass assert_parse_args( default, "Bob --user.FAVORITE_COLOR.RED=100 --user.FAVORITE_COLOR.GREEN=200 --user.FAVORITE_COLOR.BLUE=255", User("Bob", Color(100, 200, 255)), ) cyclopts-3.9.0/tests/test_new_type.py000066400000000000000000000017241475451620500200050ustar00rootroot00000000000000from typing import NewType import pytest from cyclopts._convert import token_count def test_new_type_str(app, assert_parse_args): CustomStr = NewType("CustomStr", str) @app.default def main(a: CustomStr): pass assert_parse_args(main, "foo", CustomStr("foo")) def test_new_type_token_count_str(app, assert_parse_args): CustomStr = NewType("CustomStr", str) assert (1, False) == token_count(CustomStr) @pytest.mark.parametrize( "cmd, expected", [ ("foo", ["foo"]), ("--a foo", ["foo"]), ("foo bar", ["foo", "bar"]), ("--a foo --a bar", ["foo", "bar"]), ("foo bar baz", ["foo", "bar", "baz"]), ("--a foo --a bar --a baz", ["foo", "bar", "baz"]), ], ) def test_new_type_token_count_list_str(app, assert_parse_args, cmd, expected): CustomStr = NewType("CustomStr", str) @app.default def main(a: list[CustomStr]): pass assert_parse_args(main, cmd, expected) cyclopts-3.9.0/tests/test_parameter.py000066400000000000000000000105041475451620500201270ustar00rootroot00000000000000import inspect from typing import Annotated, List, Optional, Set import pytest from cyclopts import Parameter def test_parameter_get_negatives_bool_default(): p = Parameter(name=("--foo", "--bar")) assert ("--no-foo", "--no-bar") == p.get_negatives(bool) @pytest.mark.parametrize("type_", [list, set, List[str], Set[str]]) def test_parameter_get_negatives_iterable_default(type_): p = Parameter(name=("--foo", "--bar")) assert ("--empty-foo", "--empty-bar") == p.get_negatives(type_) @pytest.mark.parametrize("type_", [list, set, List[str], Set[str]]) def test_parameter_get_negatives_iterable_custom_prefix(type_): p = Parameter(negative_iterable="vacant-", name=("--foo", "--bar")) assert ("--vacant-foo", "--vacant-bar") == p.get_negatives(type_) @pytest.mark.parametrize("type_", [list, set, List[str], Set[str]]) def test_parameter_get_negatives_iterable_custom_prefix_list(type_): p = Parameter(negative_iterable=["vacant-", "blank-"], name=("--foo", "--bar")) assert {"--vacant-foo", "--vacant-bar", "--blank-foo", "--blank-bar"} == set(p.get_negatives(type_)) def test_parameter_negative_iterable_invalid_name(app, assert_parse_args): Parameter(negative_iterable=()) # Valid with pytest.raises(ValueError): Parameter(negative_iterable="--starts-with-hyphens") @pytest.mark.parametrize("type_", [bool, list, set]) def test_parameter_get_negatives_custom_single(type_): p = Parameter(negative="--foo", name=("this-string-doesnt-matter", "neither-does-this-one")) assert ("--foo",) == p.get_negatives(type_) @pytest.mark.parametrize("type_", [bool, list, set]) def test_parameter_get_negatives_bool_custom_list(type_): p = Parameter(negative=["--foo", "--bar"], name="this-string-doesnt-matter") assert ("--foo", "--bar") == p.get_negatives(type_) @pytest.mark.parametrize("type_", [bool, list, set]) def test_parameter_get_negatives_bool_custom_prefix(type_): p = Parameter(negative_bool="yesnt-", name=("--foo", "--bar")) assert ("--yesnt-foo", "--yesnt-bar") == p.get_negatives(bool) def test_parameter_negative_bool_invalid_name(app, assert_parse_args): Parameter(negative_bool=()) # Valid with pytest.raises(ValueError): Parameter(negative_bool="--starts-with-hyphens") @pytest.mark.parametrize("type_", [bool, list, set]) def test_parameter_get_negatives_bool_custom_prefix_list(type_): p = Parameter(negative_bool=["yesnt-", "not-"], name=("--foo", "--bar")) assert {"--yesnt-foo", "--yesnt-bar", "--not-foo", "--not-bar"} == set(p.get_negatives(bool)) def test_parameter_from_annotation_basic(): expected_cparam = Parameter( name=["--help", "-h"], negative="", show_default=False, help="Display this message and exit.", ) assert (bool, expected_cparam) == Parameter.from_annotation(Annotated[bool, expected_cparam], Parameter()) def test_parameter_from_annotation_optional_annotated(): expected_cparam = Parameter( name=["--help", "-h"], negative="", show_default=False, help="Display this message and exit.", ) assert (bool, expected_cparam) == Parameter.from_annotation(Optional[Annotated[bool, expected_cparam]], Parameter()) def test_parameter_from_annotation_empty_annotation(): assert (inspect.Parameter.empty, Parameter()) == Parameter.from_annotation(inspect.Parameter.empty, Parameter()) def test_parameter_combine(): p1 = Parameter(negative="--foo") p2 = Parameter(show_default=False) p_combined = Parameter.combine(p1, None, p2) assert p_combined.negative == ("--foo",) assert p_combined.show_default is False def test_parameter_combine_priority(): p1 = Parameter(negative="--foo") p2 = Parameter(negative="--bar") p_combined = Parameter.combine(p1, p2) assert p_combined.negative == ("--bar",) def test_parameter_combine_priority_none(): p1 = Parameter(negative="--foo") p2 = Parameter(negative=None) p_combined = Parameter.combine(p1, p2) assert p_combined.negative is None def test_parameter_default(): p1 = Parameter() p2 = Parameter.default() # The two parameters should be equivalent. assert p1 == p2 # However, the _provided_args field should differ assert p1._provided_args == () # Just testing a few assert {"name", "converter", "validator"}.issubset(p2._provided_args) cyclopts-3.9.0/tests/test_parameter_allow_leading_hyphen.py000066400000000000000000000010721475451620500243630ustar00rootroot00000000000000from typing import Annotated import pytest from cyclopts import Parameter from cyclopts.exceptions import UnknownOptionError def test_allow_leading_hyphen_false(app): @app.default def foo(bar: Annotated[str, Parameter()]): pass with pytest.raises(UnknownOptionError): app("--buzz", exit_on_error=False, print_error=True) def test_allow_leading_hyphen_true(app, assert_parse_args): @app.default def foo(bar: Annotated[str, Parameter(allow_leading_hyphen=True)]): pass assert_parse_args(foo, "--buzz", bar="--buzz") cyclopts-3.9.0/tests/test_parameter_decorator.py000066400000000000000000000062201475451620500221710ustar00rootroot00000000000000from dataclasses import dataclass from textwrap import dedent from typing import Annotated, Optional import pytest from attrs import define from cyclopts import Parameter from cyclopts.exceptions import UnknownOptionError @pytest.mark.parametrize("decorator", [dataclass, define]) def test_parameter_decorator_dataclass(app, assert_parse_args, decorator): @Parameter(name="*") # Flatten namespace. @decorator class User: name: str age: int @app.command def create(*, user: Optional[User] = None): pass assert_parse_args(create, "create") assert_parse_args(create, "create --name=Bob --age=100", user=User("Bob", 100)) # pyright: ignore[reportCallIssue] @pytest.mark.parametrize("decorator", [dataclass, define]) def test_parameter_decorator_dataclass_nested_1(app, decorator, console): """ https://github.com/BrianPugh/cyclopts/issues/320 """ @decorator class S3Path: bucket: Annotated[str, Parameter()] key: Annotated[str, Parameter()] @Parameter(name="*") # Flatten namespace. @decorator class S3CliParams: path: Annotated[S3Path, Parameter(name="*")] region: Annotated[str, Parameter(name="region")] @app.command def action(*, s3_path: S3CliParams): pass with console.capture() as capture: app("action --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_parameter_decorator action [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * --bucket [required] │ │ * --key [required] │ │ * --region [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_parameter_decorator_dataclass_inheritance(app, assert_parse_args): @Parameter(name="person") # Should override the name="u" below. @Parameter(name="u", negative_bool=[]) @dataclass class User: name: str age: int privileged: bool = False @Parameter(name="a", negative_bool=None) # Should revert to Cyclopts defaults @dataclass class Admin(User): privileged: bool = True @app.command def create(*, user: Optional[User] = None, admin: Optional[Admin] = None): pass assert_parse_args(create, "create --person.name=Bob --person.age=100", user=User("Bob", 100)) with pytest.raises(UnknownOptionError): app("create --person.no-privileged", exit_on_error=False) assert_parse_args(create, "create --a.name=Bob --a.age=100", admin=Admin("Bob", 100)) assert_parse_args( create, "create --a.name=Bob --a.age=100 --a.no-privileged", admin=Admin("Bob", 100, privileged=False) ) cyclopts-3.9.0/tests/test_py312_type_alias_type.py000066400000000000000000000025401475451620500223010ustar00rootroot00000000000000from typing import Annotated, Literal, TypeAlias import pytest from cyclopts.help import _get_choices FontSize: TypeAlias = Literal[10, 12, 16] type BoxSize = Literal[10, 12, 16] def test_py312_type_alias_type(app, assert_parse_args): """Support for python3.12 :obj:`TypeAliasType`. https://github.com/BrianPugh/cyclopts/issues/190 """ @app.default def main( font_size: FontSize, box_size: BoxSize, box_size_2: Annotated[BoxSize, "foo"], ): pass assert_parse_args(main, "10 12 16", 10, 12, 16) type FontSingleFormat = Literal["otf", "woff2", "ttf", "bdf", "pcf"] type FontCollectionFormat = Literal["otc", "ttc"] FontPixelFormat: TypeAlias = Literal["bmp"] @pytest.mark.parametrize( "type_, expected", [ (FontPixelFormat, "bmp"), (FontSingleFormat, "otf, woff2, ttf, bdf, pcf"), (FontSingleFormat | FontPixelFormat, "otf, woff2, ttf, bdf, pcf, bmp"), (FontSingleFormat | FontCollectionFormat, "otf, woff2, ttf, bdf, pcf, otc, ttc"), (FontSingleFormat | FontCollectionFormat | None, "otf, woff2, ttf, bdf, pcf, otc, ttc"), (list[FontSingleFormat | FontCollectionFormat] | None, "otf, woff2, ttf, bdf, pcf, otc, ttc"), ], ) def test_py312_type_alias_type_help_get_choices(type_, expected): assert expected == _get_choices(type_, lambda x: x) cyclopts-3.9.0/tests/test_pydantic.py000066400000000000000000000260351475451620500177700ustar00rootroot00000000000000import json from datetime import datetime from textwrap import dedent from typing import Annotated, Dict, Optional, Union import pytest from pydantic import BaseModel, ConfigDict, Field, PositiveInt, validate_call from pydantic import ValidationError as PydanticValidationError from pydantic.alias_generators import to_camel from cyclopts import MissingArgumentError, Parameter def test_pydantic_error_msg(app, console): @app.command @validate_call def foo(value: PositiveInt): print(value) assert app["foo"].default_command == foo foo(1) with pytest.raises(PydanticValidationError): foo(-1) with console.capture() as capture, pytest.raises(PydanticValidationError): app(["foo", "-1"], console=console, exit_on_error=False, print_error=True) actual = capture.get() expected_prefix = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ 1 validation error for test_pydantic_error_msg..foo │ │ 0 │ │ Input should be greater than 0 [type=greater_than, │ │ input_value=-1, input_type=int] │ │ For further information visit │ """ ) assert actual.startswith(expected_prefix) # Modified from https://docs.pydantic.dev/latest/#pydantic-examples class Outfit(BaseModel): body: str head: str has_socks: bool class User(BaseModel): id: int name: str = Field(default="John Doe") signup_ts: Union[datetime, None] tastes: Dict[str, PositiveInt] outfit: Optional[Outfit] = None def test_bind_pydantic_basemodel(app, assert_parse_args): @app.command def foo(user: User): pass external_data = { "id": 123, "signup_ts": "2019-06-01 12:22", "tastes": { "wine": 9, b"cheese": 7, "cabbage": "1", }, "outfit": { "body": "t-shirt", "head": "baseball-cap", "has_socks": True, }, } assert_parse_args( foo, 'foo --user.id=123 --user.signup-ts="2019-06-01 12:22" --user.tastes.wine=9 --user.tastes.cheese=7 --user.tastes.cabbage=1 --user.outfit.body=t-shirt --user.outfit.head=baseball-cap --user.outfit.has-socks', User(**external_data), ) def test_bind_pydantic_basemodel_from_json(app, assert_parse_args, monkeypatch): @app.command def foo(user: Annotated[User, Parameter(env_var="USER")]): pass external_data = { "id": 123, "signup_ts": "2019-06-01 12:22", "tastes": { "wine": 9, "cheese": 7, "cabbage": "1", }, "outfit": { "body": "t-shirt", "head": "baseball-cap", "has_socks": True, }, } monkeypatch.setenv("USER", json.dumps(external_data)) assert_parse_args( foo, 'foo --user.id=123 --user.signup-ts="2019-06-01 12:22" --user.tastes.wine=9 --user.tastes.cheese=7 --user.tastes.cabbage=1 --user.outfit.body=t-shirt --user.outfit.head=baseball-cap --user.outfit.has-socks', User(**external_data), ) def test_bind_pydantic_basemodel_help(app, console): @app.default def foo(user: User): pass with console.capture() as capture: app("--help", console=console) actual = capture.get() expected = dedent( """\ Usage: foo COMMAND [ARGS] [OPTIONS] ╭─ Commands ─────────────────────────────────────────────────────────╮ │ --help -h Display this message and exit. │ │ --version Display application version. │ ╰────────────────────────────────────────────────────────────────────╯ ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * USER.ID --user.id [required] │ │ USER.NAME --user.name [default: John Doe] │ │ * USER.SIGNUP-TS [required] │ │ --user.signup-ts │ │ * --user.tastes [required] │ │ --user.outfit.body │ │ --user.outfit.head │ │ --user.outfit.has-socks - │ │ -user.outfit.no-has-soc │ │ ks │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_bind_pydantic_basemodel_missing_arg(app, console): """Partially defining an Outfit should raise a MissingArgumentError.""" @app.command def foo(user: User): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app.parse_args( 'foo --user.id=123 --user.signup-ts="2019-06-01 12:22" --user.tastes.wine=9 --user.tastes.cheese=7 --user.tastes.cabbage=1 --user.outfit.body=t-shirt', console=console, exit_on_error=False, ) actual = capture.get() expected = dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "foo" parameter "--user.outfit.head" requires an argument. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected def test_pydantic_alias_1(app, console, assert_parse_args): class User(BaseModel): model_config = ConfigDict( # A callable that takes a field name and returns an alias for it. alias_generator=to_camel, # Whether an aliased field may be populated by its name as given by the model attribute, as well as the alias. # e.g. for this model, both "user_name=" and "userName=" should work. populate_by_name=True, # Whether to build models and look up discriminators of tagged unions using python object attributes. from_attributes=True, ) user_name: str "Name of user." age_in_years: int "Age of user in years." @app.command def foo(user: User): pass with console.capture() as capture: app("foo --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_pydantic foo [ARGS] [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * USER.USER-NAME Name of user. [required] │ │ --user.user-name │ │ --user.username │ │ * USER.AGE-IN-YEARS Age of user in years. [required] │ │ --user.age-in-years │ │ --user.ageinyears │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected assert_parse_args( foo, "foo --user.username='Bob Smith' --user.age_in_years=100", user=User(user_name="Bob Smith", age_in_years=100), ) @pytest.mark.parametrize( "env_var", [ '{"storage_class": "longhorn"}', '{"storageclass": "longhorn"}', # check for incorrectly parsing "null" as a string '{"storage_class": "longhorn", "limit": null}', ], ) def test_pydantic_alias_env_var_json(app, assert_parse_args, monkeypatch, env_var): """ https://github.com/BrianPugh/cyclopts/issues/332 """ monkeypatch.setenv("SPEC", env_var) class BaseK8sModel(BaseModel): model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, from_attributes=True, ) class Spec(BaseK8sModel): storage_class: str limit: Optional[int] = None @app.default def run(spec: Annotated[Spec, Parameter(env_var="SPEC")]) -> None: pass assert_parse_args(run, "", Spec(storage_class="longhorn")) def test_parameter_decorator_pydantic_nested_1(app, console): """ https://github.com/BrianPugh/cyclopts/issues/320 See Also -------- test_parameter_decorator_dataclass_nested_1 """ class S3Path(BaseModel): bucket: Annotated[str, Parameter()] key: str @Parameter(name="*") # Flatten namespace. class S3CliParams(BaseModel): path: Annotated[S3Path, Parameter(name="*")] region: Annotated[str, Parameter(name="area")] @app.command def action(*, s3_path: S3CliParams): pass with console.capture() as capture: app("action --help", console=console) actual = capture.get() expected = dedent( """\ Usage: test_pydantic action [OPTIONS] ╭─ Parameters ───────────────────────────────────────────────────────╮ │ * --bucket [required] │ │ * --key [required] │ │ * --area [required] │ ╰────────────────────────────────────────────────────────────────────╯ """ ) assert actual == expected cyclopts-3.9.0/tests/test_run.py000066400000000000000000000005661475451620500167620ustar00rootroot00000000000000from pytest import MonkeyPatch from cyclopts import App, run def test_run(monkeypatch: MonkeyPatch): def main(input: int) -> int: return input * 2 class TestApp(App): def __call__(self, tokens=None, **kwargs): return super().__call__("2", **kwargs) monkeypatch.setitem(run.__globals__, "App", TestApp) assert run(main) == 4 cyclopts-3.9.0/tests/test_runtime_exceptions.py000066400000000000000000000146271475451620500221050ustar00rootroot00000000000000from textwrap import dedent from typing import Tuple import pytest from cyclopts import CycloptsError from cyclopts.exceptions import MissingArgumentError @pytest.fixture def mock_get_function_info(mocker): mocker.patch("cyclopts.exceptions._get_function_info", return_value=("FILENAME", 100)) def test_runtime_exception_not_enough_tokens(app, console, mock_get_function_info): @app.default def foo(a: Tuple[int, int, int]): pass with console.capture() as capture, pytest.raises(CycloptsError): app(["1", "2"], exit_on_error=False, console=console) actual = capture.get() assert actual == ( "╭─ Error ────────────────────────────────────────────────────────────╮\n" '│ Parameter "--a" requires 3 positional arguments. Only got 2. │\n' "╰────────────────────────────────────────────────────────────────────╯\n" ) with console.capture() as capture, pytest.raises(CycloptsError): app(["1", "2"], exit_on_error=False, console=console, verbose=True) actual = capture.get() assert actual == dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ MissingArgumentError │ │ Function defined in file "FILENAME", line 100: │ │ foo(a: Tuple[int, int, int]) │ │ Root Input Tokens: ['1', '2'] │ │ Parameter "--a" requires 3 positional arguments. Only got 2. │ │ Parsed: ['1', '2']. │ ╰────────────────────────────────────────────────────────────────────╯ """ ) def test_runtime_exception_missing_parameter(app, console): @app.default def foo(a, b, c): pass with console.capture() as capture, pytest.raises(CycloptsError): app(["1", "2"], exit_on_error=False, console=console) actual = capture.get() assert actual == ( "╭─ Error ────────────────────────────────────────────────────────────╮\n" '│ Parameter "--c" requires an argument. │\n' "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_runtime_exception_bad_command(app, console): with console.capture() as capture, pytest.raises(CycloptsError): app(["bad-command", "123"], exit_on_error=False, console=console) actual = capture.get() assert actual == ( "╭─ Error ────────────────────────────────────────────────────────────╮\n" '│ Unknown command "bad-command". │\n' "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_runtime_exception_bad_command_recommend(app, console): @app.command def mad_command(): pass with console.capture() as capture, pytest.raises(CycloptsError): app(["bad-command", "123"], exit_on_error=False, console=console) actual = capture.get() assert actual == ( "╭─ Error ────────────────────────────────────────────────────────────╮\n" '│ Unknown command "bad-command". Did you mean "mad-command"? │\n' "╰────────────────────────────────────────────────────────────────────╯\n" ) def test_runtime_exception_bad_parameter_recommend(app, console): @app.command def some_command(*, foo: int): pass with console.capture() as capture, pytest.raises(MissingArgumentError): app(["some-command", "--boo", "123"], exit_on_error=False, console=console) actual = capture.get() assert actual == dedent( """\ ╭─ Error ────────────────────────────────────────────────────────────╮ │ Command "some-command" parameter "--foo" requires an argument. Did │ │ you mean "--foo" instead of "--boo"? │ ╰────────────────────────────────────────────────────────────────────╯ """ ) def test_runtime_exception_repeat_arguments(app, console): @app.default def foo(a): pass with console.capture() as capture, pytest.raises(CycloptsError): app(["--a=1", "--a=2"], exit_on_error=False, console=console) actual = capture.get() assert actual == ( "╭─ Error ────────────────────────────────────────────────────────────╮\n" "│ Parameter --a specified multiple times. │\n" "╰────────────────────────────────────────────────────────────────────╯\n" ) cyclopts-3.9.0/tests/test_subapp.py000066400000000000000000000034371475451620500174500ustar00rootroot00000000000000import pytest from cyclopts import App, Parameter from cyclopts.core import resolve_default_parameter_from_apps def test_subapp_basic(app): @app.command def foo(a: int, b: int, c: int): return a + b + c app.command(bar := App(name="bar")) @bar.command def fizz(a: int, b: int, c: int): return a - b - c @bar.command def buzz(): return 100 @bar.default def default(a: int): return 100 * a assert 6 == app("foo 1 2 3") assert -4 == app("bar fizz 1 2 3") assert 100 == app("bar buzz") assert 200 == app("bar 2") def test_subapp_must_have_name(app): with pytest.raises(ValueError): app.command(App()) # Failure on attempting to register an app without an explicit name. app.command(App(), name="foo") # However, this is fine. def test_subapp_registering_cannot_have_other_kwargs(app): with pytest.raises(ValueError): app.command(App(name="foo"), help="this is invalid.") def test_subapp_cannot_be_default(app): with pytest.raises(TypeError): app.default(App(name="foo")) with pytest.raises(TypeError): App(default_command=App(name="foo")) def test_resolve_default_parameter_1(): parent_app_1 = App(default_parameter=Parameter("foo")) sub_app = App(name="bar") parent_app_1.command(sub_app) actual_parameter = resolve_default_parameter_from_apps([parent_app_1, sub_app]) assert actual_parameter == Parameter("foo") def test_resolve_default_parameter_2(): parent_app_1 = App(default_parameter=Parameter("foo")) sub_app = App(name="bar", default_parameter=Parameter("bar")) parent_app_1.command(sub_app) actual_parameter = resolve_default_parameter_from_apps([parent_app_1, sub_app]) assert actual_parameter == Parameter("bar") cyclopts-3.9.0/tests/test_utils.py000066400000000000000000000010151475451620500173040ustar00rootroot00000000000000import pytest from cyclopts.utils import Sentinel, grouper def test_grouper(): assert [(1,), (2,), (3,), (4,)] == list(grouper([1, 2, 3, 4], 1)) assert [(1, 2), (3, 4)] == list(grouper([1, 2, 3, 4], 2)) assert [(1, 2, 3, 4)] == list(grouper([1, 2, 3, 4], 4)) with pytest.raises(ValueError): grouper([1, 2, 3, 4], 3) def test_sentinel(): class SENTINEL_VALUE(Sentinel): # noqa: N801 pass assert str(SENTINEL_VALUE) == "" assert bool(SENTINEL_VALUE) is False cyclopts-3.9.0/tests/test_validate_command.py000066400000000000000000000010021475451620500214270ustar00rootroot00000000000000from typing import Tuple, Union from cyclopts.parameter import validate_command def test_validate_command(): def f1(): pass validate_command(f1) def f2(a, b, c): pass validate_command(f2) def f3(a: str, b: int, c: list): pass validate_command(f3) def f4(a: Tuple[int, int], b: str): pass validate_command(f4) # Python automatically deduplicates the double None. def f5(a: Union[None, None]): pass validate_command(f5) cyclopts-3.9.0/tests/test_version.py000066400000000000000000000033331475451620500176360ustar00rootroot00000000000000def test_version_print_console_from_init(app, console): app.console = console with console.capture() as capture: app.version_print() assert "0.0.0\n" == capture.get() def test_version_print_console_from_method(app, console): with console.capture() as capture: app.version_print(console) assert "0.0.0\n" == capture.get() def test_version_print_console_none(app, console): app.version = None with console.capture() as capture: app.version_print(console) assert "0.0.0\n" == capture.get() def test_version_print_custom_string(app, console): """The asterisks also test to make sure the proper help_format is being used.""" app.version = "**foo**" with console.capture() as capture: app.version_print(console) assert "foo\n" == capture.get() def test_version_print_custom_callable(app, console): def my_version(): return "**foo**" app.version = my_version with console.capture() as capture: app.version_print(console) assert "foo\n" == capture.get() def test_version_print_help_format_fallback(app, console): """If no explicit version_format is provided, we should fallback to help_format.""" app.help_format = "rich" app.version = "[red]foo[/red]" with console.capture() as capture: app.version_print(console) assert "foo\n" == capture.get() def test_version_print_help_format_override(app, console): """If version_format is provided, help_format should not be used for version.""" app.help_format = "plain" app.version_format = "rich" app.version = "[red]foo[/red]" with console.capture() as capture: app.version_print(console) assert "foo\n" == capture.get() cyclopts-3.9.0/tests/test_version_parameter.py000066400000000000000000000012061475451620500216730ustar00rootroot00000000000000"""From issue #219.""" import pytest @pytest.mark.parametrize( "cmd", [ "foo --version 1.2.3", "foo --version=1.2.3", ], ) def test_version_subapp_version_parameter(app, assert_parse_args, cmd): @app.command(version_flags=[]) def foo(version: str): pass assert_parse_args(foo, cmd, version="1.2.3") @pytest.mark.parametrize( "cmd", [ "foo --help 1.2.3", "foo --help=1.2.3", ], ) def test_version_subapp_help_parameter(app, assert_parse_args, cmd): @app.command(help_flags=[]) def foo(help: str): pass assert_parse_args(foo, cmd, help="1.2.3") cyclopts-3.9.0/tests/types/000077500000000000000000000000001475451620500157025ustar00rootroot00000000000000cyclopts-3.9.0/tests/types/test_types_json.py000066400000000000000000000002031475451620500215030ustar00rootroot00000000000000from cyclopts import types as ct def test_types_json(convert): assert {"foo": "bar"} == convert(ct.Json, ['{"foo": "bar"}']) cyclopts-3.9.0/tests/types/test_types_number.py000066400000000000000000000026241475451620500220330ustar00rootroot00000000000000from typing import Optional import pytest from cyclopts.exceptions import ValidationError from cyclopts.types import UInt8 def test_nested_annotated_validator(app, assert_parse_args): @app.default def default(color: tuple[UInt8, UInt8, UInt8] = (0x00, 0x00, 0x00)): pass assert_parse_args(default, "0x12 0x34 0x56", (0x12, 0x34, 0x56)) with pytest.raises(ValidationError) as e: app.parse_args("100 200 300", exit_on_error=False) assert str(e.value) == 'Invalid value "300" for "COLOR". Must be <= 255.' with pytest.raises(ValidationError) as e: app.parse_args("--color 100 200 300", exit_on_error=False) assert str(e.value) == 'Invalid value "300" for "--color". Must be <= 255.' def test_nested_list_annotated_validator(app, assert_parse_args): @app.default def default(color: Optional[list[tuple[UInt8, UInt8, UInt8]]] = None): pass assert_parse_args( default, "0x12 0x34 0x56 0x78 0x90 0xAB", [(0x12, 0x34, 0x56), (0x78, 0x90, 0xAB)], ) with pytest.raises(ValidationError) as e: app.parse_args("100 200 300", exit_on_error=False) assert str(e.value) == 'Invalid value "300" for "COLOR". Must be <= 255.' with pytest.raises(ValidationError) as e: app.parse_args("--color 100 200 300", exit_on_error=False) assert str(e.value) == 'Invalid value "300" for "--color". Must be <= 255.' cyclopts-3.9.0/tests/types/test_types_path.py000066400000000000000000000144441475451620500215020ustar00rootroot00000000000000from pathlib import Path from typing import Tuple import pytest from cyclopts import types as ct from cyclopts.exceptions import ValidationError @pytest.fixture def tmp_file(tmp_path): file_path = tmp_path / "file.bin" file_path.touch() return file_path # ExistingPath def test_types_existing_path(convert, tmp_file): assert tmp_file == convert(ct.ExistingPath, tmp_file) def test_types_existing_path_validation_error(convert, tmp_path): with pytest.raises(ValidationError): convert(ct.ExistingPath, tmp_path / "foo") # ExistingFile def test_types_existing_file(convert, tmp_file): assert tmp_file == convert(ct.ExistingFile, tmp_file) def test_types_existing_file_app(app): """https://github.com/BrianPugh/cyclopts/issues/287""" @app.default def main(f: ct.ExistingFile): pass with pytest.raises(ValidationError): app(["this-file-does-not-exist"], exit_on_error=False) def test_types_existing_file_app_list(app): """https://github.com/BrianPugh/cyclopts/issues/287""" @app.default def main(f: list[ct.ExistingFile]): pass with pytest.raises(ValidationError): app(["this-file-does-not-exist"], exit_on_error=False) def test_types_existing_file_validation_error(convert, tmp_path): with pytest.raises(ValidationError): convert(ct.ExistingFile, tmp_path) # ExistingDirectory def test_types_existing_directory(convert, tmp_path): assert tmp_path == convert(ct.ExistingDirectory, tmp_path) def test_types_existing_directory_validation_error(convert, tmp_file): with pytest.raises(ValidationError): convert(ct.ExistingDirectory, tmp_file) # Directory def test_types_directory(convert, tmp_path): assert tmp_path == convert(ct.Directory, tmp_path) def test_types_directory_validation_error(convert, tmp_file): with pytest.raises(ValidationError): convert(ct.Directory, tmp_file) # File def test_types_file(convert, tmp_file): assert tmp_file == convert(ct.File, tmp_file) def test_types_file_validation_error(convert, tmp_path): with pytest.raises(ValidationError): convert(ct.File, tmp_path) # ResolvedExistingPath @pytest.mark.parametrize("action", ["touch", "mkdir"]) def test_types_resolved_existing_path(convert, tmp_path, action): src = tmp_path / ".." / tmp_path.name / "foo" getattr(src, action)() assert src.resolve() == convert(ct.ResolvedExistingPath, src) def test_types_resolved_existing_path_list(app, assert_parse_args): @app.default def main(f: list[ct.ResolvedFile]): pass expected = Path("foo.bin").resolve() assert_parse_args(main, "foo.bin", [expected]) def test_types_resolved_existing_path_validation_error(convert, tmp_path): with pytest.raises(ValidationError): convert(ct.ResolvedExistingPath, tmp_path / "foo") # ResolvedExistingFile def test_types_resolved_existing_file(convert, tmp_path): src = tmp_path / ".." / tmp_path.name / "foo" src.touch() assert src.resolve() == convert(ct.ResolvedExistingFile, src) def test_types_resolved_existing_file_validation_error(convert, tmp_path): with pytest.raises(ValidationError): convert(ct.ResolvedExistingFile, tmp_path / "foo") # ResolvedExistingDirectory def test_types_resolved_existing_directory(convert, tmp_path): src = tmp_path / ".." / tmp_path.name / "foo" src.mkdir() assert src.resolve() == convert(ct.ResolvedExistingDirectory, src) def test_types_resolved_existing_directory_validation_error(convert, tmp_path): src = tmp_path / ".." / tmp_path.name / "foo" with pytest.raises(ValidationError): convert(ct.ResolvedExistingDirectory, src) # ResolvedDirectory def test_types_resolved_directory(convert, tmp_path): assert tmp_path == convert(ct.ResolvedDirectory, tmp_path / "foo" / "..") def test_types_resolved_directory_validation_error(convert, tmp_file): with pytest.raises(ValidationError): convert(ct.ResolvedDirectory, tmp_file) # ResolvedFile def test_types_resolved_file(convert, tmp_path): src = tmp_path / ".." / tmp_path.name / "foo" src.touch() assert src.resolve() == convert(ct.ResolvedFile, src) def test_types_resolved_file_validation_error(convert, tmp_path): with pytest.raises(ValidationError): convert(ct.ResolvedFile, tmp_path) # Misc def test_types_path_resolve_converter(convert, tmp_path): """Tests that ``_path_resolve_converter`` handles things like tuples correctly.""" dir1 = tmp_path / "foo" dir2 = tmp_path / "bar" dir1.mkdir() dir2.mkdir() actual = convert(Tuple[ct.ResolvedDirectory, ct.ResolvedDirectory], [dir1.as_posix(), dir2.as_posix()]) assert (dir1, dir2) == actual # File extensions @pytest.mark.parametrize( "annotation, ext", [ (ct.BinPath, "bin"), (ct.CsvPath, "csv"), (ct.TxtPath, "txt"), (ct.ImagePath, "jpg"), (ct.ImagePath, "jpeg"), (ct.ImagePath, "png"), (ct.Mp4Path, "mp4"), (ct.JsonPath, "json"), (ct.TomlPath, "toml"), (ct.YamlPath, "yaml"), ], ) def test_types_file_extensions_good(annotation, ext, convert, tmp_path): path = tmp_path / f"foo.{ext}" convert(annotation, path) # File extensions @pytest.mark.parametrize( "annotation, ext", [ (ct.ExistingBinPath, "bin"), (ct.ExistingCsvPath, "csv"), (ct.ExistingTxtPath, "txt"), (ct.ExistingImagePath, "jpg"), (ct.ExistingImagePath, "jpeg"), (ct.ExistingImagePath, "png"), (ct.ExistingMp4Path, "mp4"), (ct.ExistingJsonPath, "json"), (ct.ExistingTomlPath, "toml"), (ct.ExistingYamlPath, "yaml"), ], ) def test_types_file_extensions_exist_good(annotation, ext, convert, tmp_path): path = tmp_path / f"foo.{ext}" with pytest.raises(ValidationError): # File has not been created yet convert(annotation, path) path.touch() convert(annotation, path) @pytest.mark.parametrize( "annotation", [ ct.BinPath, ct.CsvPath, ct.TxtPath, ct.ImagePath, ct.Mp4Path, ct.JsonPath, ct.TomlPath, ct.YamlPath, ], ) def test_types_file_extensions_bad(annotation, convert, tmp_path): path = tmp_path / "foo.bar" with pytest.raises(ValidationError): convert(annotation, path) cyclopts-3.9.0/tests/validators/000077500000000000000000000000001475451620500167065ustar00rootroot00000000000000cyclopts-3.9.0/tests/validators/test_validator_group.py000066400000000000000000000104361475451620500235240ustar00rootroot00000000000000from typing import Annotated import pytest from cyclopts import Argument, Group, Parameter, Token from cyclopts.argument import ArgumentCollection from cyclopts.exceptions import ValidationError from cyclopts.validators import LimitedChoice @pytest.fixture def argument_collection_0(): return ArgumentCollection( [ Argument(parameter=Parameter(name="--foo")), Argument(parameter=Parameter(name="--bar")), Argument(parameter=Parameter(name="--baz")), ] ) @pytest.fixture def argument_collection_1(): return ArgumentCollection( [ Argument( tokens=[Token(keyword="--foo", value="100", source="test")], parameter=Parameter(name="--foo"), value=100, ), Argument(parameter=Parameter(name="--bar")), Argument(parameter=Parameter(name="--baz")), ] ) @pytest.fixture def argument_collection_2(): return ArgumentCollection( [ Argument( tokens=[Token(keyword="--foo", value="100", source="test")], parameter=Parameter(name="--foo"), value=100, ), Argument( tokens=[Token(keyword="--bar", value="200", source="test")], parameter=Parameter(name="--bar"), value=200, ), Argument(parameter=Parameter(name="--baz")), ] ) @pytest.fixture def argument_collection_3(): return ArgumentCollection( [ Argument( tokens=[Token(keyword="--foo", value="100", source="test")], parameter=Parameter(name="--foo"), value=100, ), Argument( tokens=[Token(keyword="--bar", value="200", source="test")], parameter=Parameter(name="--bar"), value=200, ), Argument( tokens=[Token(keyword="--baz", value="300", source="test")], parameter=Parameter(name="--baz"), value=300, ), ] ) def test_limited_choice_default_success(argument_collection_0, argument_collection_1): """Mutually-exclusive functionality.""" validator = LimitedChoice() validator(argument_collection_0) validator(argument_collection_1) @pytest.mark.parametrize("min", [None, 1]) def test_limited_choice_default_failure(min, argument_collection_2): """Mutually-exclusive functionality.""" if min is None: validator = LimitedChoice() else: validator = LimitedChoice(min) validator = LimitedChoice() with pytest.raises(ValueError): validator(argument_collection_2) def test_limited_choice_default_min_max( argument_collection_0, argument_collection_1, argument_collection_2, argument_collection_3 ): validator = LimitedChoice(1, 2) with pytest.raises(ValueError): validator(argument_collection_0) validator(argument_collection_1) validator(argument_collection_2) with pytest.raises(ValueError): validator(argument_collection_3) def test_limited_choice_invalid_min_max(): with pytest.raises(ValueError): LimitedChoice(2, 1) def test_bind_group_validator_limited_choice(app): @app.command def foo( *, car: Annotated[bool, Parameter(group=Group("Vehicle", validator=LimitedChoice()))] = False, motorcycle: Annotated[bool, Parameter(group="Vehicle")] = False, ): pass with pytest.raises(ValidationError) as e: app("foo --car --motorcycle", exit_on_error=False) assert str(e.value) == 'Invalid values for group "Vehicle". Mutually exclusive arguments: {--car, --motorcycle}' app("foo") app("foo --car") app("foo --motorcycle") def test_bind_group_validator_limited_choice_name_override(app): @app.command def foo( *, car: Annotated[bool, Parameter(group=Group("Vehicle", validator=LimitedChoice()))] = False, motorcycle: Annotated[bool, Parameter(name="--bike", group="Vehicle")] = False, ): pass with pytest.raises(ValidationError) as e: app("foo --car --bike", exit_on_error=False) assert str(e.value) == 'Invalid values for group "Vehicle". Mutually exclusive arguments: {--car, --bike}' cyclopts-3.9.0/tests/validators/test_validator_number.py000066400000000000000000000033471475451620500236630ustar00rootroot00000000000000import pytest from cyclopts.validators import Number def test_validator_number_type(): validator = Number() with pytest.raises(TypeError): validator(int, "this is a string.") # pyright: ignore[reportArgumentType] def test_validator_number_lt(): validator = Number(lt=5) validator(int, 0) with pytest.raises(ValueError): validator(int, 5) with pytest.raises(ValueError): validator(int, 6) def test_validator_number_lt_sequence(): validator = Number(lt=5) validator(int, (0, 0, 0)) validator(int, (0, 0, (1, 2))) with pytest.raises(ValueError): validator(int, 5) with pytest.raises(ValueError): validator(int, 6) with pytest.raises(ValueError): validator(int, (0, 0, 6)) with pytest.raises(ValueError): validator(int, (0, 0, (1, 6))) def test_validator_number_lte(): validator = Number(lte=5) validator(int, 0) validator(int, 5) with pytest.raises(ValueError): validator(int, 6) def test_validator_number_gt(): validator = Number(gt=5) validator(int, 10) with pytest.raises(ValueError): validator(int, 5) with pytest.raises(ValueError): validator(int, 4) def test_validator_number_gte(): validator = Number(gte=5) validator(int, 10) validator(int, 5) with pytest.raises(ValueError): validator(int, 4) def test_validator_number_modulo(): validator = Number(modulo=4) validator(int, 8) validator(float, 8.0) with pytest.raises(ValueError): validator(int, 9) def test_validator_number_typeerror(): validator = Number(gte=5) with pytest.raises(TypeError): validator(str, "foo") # pyright: ignore[reportArgumentType] cyclopts-3.9.0/tests/validators/test_validator_path.py000066400000000000000000000050461475451620500233250ustar00rootroot00000000000000from pathlib import Path import pytest from cyclopts import validators def test_path_type(tmp_path): validator = validators.Path() validator(Path, Path(tmp_path) / "does-not-exist") # default configuration doesn't really check much. with pytest.raises(TypeError): validator(Path, "this is a string.") # pyright: ignore[reportArgumentType] def test_path_exists(tmp_path): validator = validators.Path(exists=True) validator(Path, tmp_path) with pytest.raises(ValueError): validator(Path, tmp_path / "foo") def test_path_exists_sequence(tmp_path): validator = validators.Path() validator(tuple[Path, Path], (tmp_path, tmp_path)) validator(list[Path], [tmp_path, tmp_path]) def test_path_file_okay(tmp_path): validator = validators.Path(file_okay=False) folder = tmp_path / "directory" folder.mkdir() validator(Path, folder) file = tmp_path / "file" file.touch() with pytest.raises(ValueError): validator(Path, file) def test_path_dir_okay(tmp_path): validator = validators.Path(dir_okay=False) folder = tmp_path / "directory" folder.mkdir() with pytest.raises(ValueError): validator(Path, folder) file = tmp_path / "file" file.touch() validator(Path, file) def test_path_invalid_values(): with pytest.raises(ValueError): validators.Path(exists=True, dir_okay=False, file_okay=False) @pytest.mark.parametrize( "ext", ["mp4", ".mp4", "Mp4", ".Mp4"], ) def test_path_extension_match_single(ext): validator = validators.Path(ext=ext) validator(Path, Path("foo.mp4")) @pytest.mark.parametrize( "ext", ["png", ".png"], ) def test_path_extension_not_match_single(ext): validator = validators.Path(ext=ext) with pytest.raises(ValueError) as e: validator(Path, Path("foo.mp4")) assert str(e.value) == '"foo.mp4" must have extension "png".' @pytest.mark.parametrize( "ext", [ ("mp4", "avi"), (".mp4", ".avi"), ("Mp4", ".AVI"), (".Mp4", ".avi"), ], ) def test_path_extension_match_multiple(ext): validator = validators.Path(ext=ext) validator(Path, Path("foo.mp4")) validator(Path, Path("foo.avi")) @pytest.mark.parametrize( "ext", [(".png", ".jpg"), ("png", "jPG")], ) def test_path_extension_not_match_multi(ext): validator = validators.Path(ext=ext) with pytest.raises(ValueError) as e: validator(Path, Path("foo.mp4")) assert str(e.value) == '"foo.mp4" does not match one of supported extensions {"png", "jpg"}.'