pax_global_header00006660000000000000000000000064151351505250014514gustar00rootroot0000000000000052 comment=455fd84a9be7537edee6d5ceca79c08e6f1ed360 rassumfrassum-0.3.3/000077500000000000000000000000001513515052500144325ustar00rootroot00000000000000rassumfrassum-0.3.3/.github/000077500000000000000000000000001513515052500157725ustar00rootroot00000000000000rassumfrassum-0.3.3/.github/workflows/000077500000000000000000000000001513515052500200275ustar00rootroot00000000000000rassumfrassum-0.3.3/.github/workflows/publish.yml000066400000000000000000000014411513515052500222200ustar00rootroot00000000000000name: Publish to PyPI on: push: tags: - 'v*' jobs: build-and-publish: runs-on: ubuntu-latest environment: pypi permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing contents: write # Need write permission to create releases steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install build dependencies run: | python -m pip install --upgrade pip python -m pip install build - name: Build package run: python -m build - name: Create GitHub Release uses: softprops/action-gh-release@v1 - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 rassumfrassum-0.3.3/.github/workflows/test.yml000066400000000000000000000005151513515052500215320ustar00rootroot00000000000000name: Tests on: push: branches: [ master ] pull_request: branches: [ master ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Run tests run: test/run-all.sh rassumfrassum-0.3.3/.gitignore000066400000000000000000000001231513515052500164160ustar00rootroot00000000000000venv/ __pycache__/ *.pyc *.pyo *.pyd .Python *.so *.egg-info/ dist/ build/ uv.lock rassumfrassum-0.3.3/LICENSE000066400000000000000000001045141513515052500154440ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .rassumfrassum-0.3.3/README.md000066400000000000000000000335331513515052500157200ustar00rootroot00000000000000[![Tests](https://github.com/joaotavora/rassumfrassum/actions/workflows/test.yml/badge.svg)][build-status] [![PyPI version](https://img.shields.io/pypi/v/rassumfrassum)](https://pypi.org/project/rassumfrassum/) # rassumfrassum Connect an LSP client to multiple LSP servers. The `rass` program, the main entry point, behaves like an LSP stdio server, so clients think they are talking to single LSP server, even though they are secretly talking to many. Behind the scenes more stdio [LSP][lsp] server subprocesses are spawned. Zero dependencies beyond Python standard library (3.10+). ![demo](./doc/demo.gif) ## Setup Install the `rass` tool and some language servers, say, Python's [ty][ty] and [ruff][ruff]: ```bash pip install rassumfrassum ty ruff ``` Now teach your LSP client to call `rass`: * In Emacs's [Eglot][eglot], find a Python file in a project and `C-u M-x eglot RET rass python RET`. * In vanilla [Neovim][neovim], use this snippet (briefly tested with `nvim --clean -u snippet.lua`) ```lua vim.lsp.config('rass-python', { cmd = {'rass','python'}, filetypes = { 'python' }, root_markers = { '.git', }, }) vim.lsp.enable('rass-python') ``` ## Command line `rass python` is the equivalent of ```bash rass -- ty server -- ruff server ``` which works just as well. You can compose as many servers as you want this way. See `rass --help` for more help. The `rass` program executable is installed by the package manager. If you need to run this from a Git checkout with no installation at all: ```bash export PYTHONPATH=$PWD/src python3 -m rassumfrassum -- ty server -- ruff server ``` ## Presets Presets give you a uniform way to start typical sets of language servers for a given language, while being flexible enough for tweaking. Many presets are simple and are just Python files with a `servers()` function that returns a list of server commands. So-called hooking presets hook into LSP messages to hide the typical initialization/configuration pains from clients, see [vue.py][vue-preset]. ### Using Presets The bundled `python` preset runs [ty][ty] and [ruff][ruff]: ```bash rass python ``` You can add more servers on top of a preset using `--` separators. For example, to add [codebook][codebook] for spell checking: ```bash rass python -- codebook-lsp server ``` ### Bundled presets It's early days and Rassumfrassum bundles only a few of these. Some are very simple, and some are slightly more complex. * `tyruff`: for `ty` + `ruff` (simple) * `basedruff`: for `basedpyright-langserver` + `ruff` (simple) * `tslint`: for `typescript-language-server` + `eslint` (complex) * `vuetail`: for `vue-language-server` + `tailwindcss-language-server` (complex) The "complex" presets use special hooks of the 'LspLogic' class to massage the exchanged messages. The servers in question (usually the JS-land ones) unfortunately weren't designed to startup without copious amounts of handholding given to them by specific clients (usually VSCode). ### User Presets You can create your own presets or override bundled ones. Rass searches these locations in order: 1. `$XDG_CONFIG_HOME/rassumfrassum/` (if XDG_CONFIG_HOME is set) 2. `~/.config/rassumfrassum/` (default) 3. `~/.rassumfrassum/` (legacy) 4. Bundled presets directory (last resort) To use [pylsp][pylsp] and ruff create, say, `~/.config/rassumfrassum/pylspruff.py`: ```python """Python preset using pylsp instead of ty.""" def servers(): return [ ['ty', 'server'], ['ruff', 'server'] ] ``` ## Performance Performance is always a question, and it's early days. But some of the optimizations that rass makes, like caching the `data` cookies of code actions, completions and diagnostics and not sending them to the client may make a non-negligible difference in your client's performance. The `ruff` server in sometimes sends more than half its weight of diagnostics lists in `data` cookies. Other more aggressive optimizations are possible in the future, like capping diagnostics and completions. Python seems to be "fast enough". Early measurements show rass to spend 8x as much time waiting for input/output as running instructions. This makes sense as most of its work is redirecting messages around, doing the odd JSON sniffing/injection here and there. See also the experimental [streaming diagnostics extension](#streaming-diagnostics-protocol-extension) section for another potential optimization opportunity for clients. ### Architecture The codebase lives in `src/rassumfrassum/` and is split into several modules: - `main.py` is the main entry point with command-line processing and argument parsing. It calls `run_multiplexer` from `rassum.py` to start the multiplexer. - `presets.py` handles preset discovery and loading, searching user config directories (XDG-compliant) and bundled presets. - `rassum.py` contains `run_multiplexer` which starts a bunch of async tasks to read from the clients and servers, and waits for all of them. The local lexical state in `run_multiplexer` tracks JSONRPC requests, responses, and notifications, and crucially the progress of ongoing aggregation attempts. In as much as possible, `rassum.py` should be just a JSONRPC-aggregator and not know anything about particular custom handling of LSP message types. There are a few violations of this principle, but whenever it needs to know what to do, it asks/informs the upper layer in `frassum.py` about in-transit messages. - `frassum.py` contains the business logic used by `rassum.py` facilities. This one fully knows about LSP. So it knows, for example, how to merge `initialize` and `shutdown` responses, when to reject a stale `textDocument/publishDiagnostics` and how to do the actual work for aggregation. - `util.py` provides logging utilities and general-purpose helpers like dict merging for debugging and monitoring the multiplexer's operation. - `test.py` contains test utilities used by both client and server test scripts. - `json.py` handles bare JSON-over-stdio logistics and is completely ignorant of LSP. It deals with protocol framing and I/O operations. ### Testing There are tests under `test/`. Each test is a subdir, usually with a `client.py`, a `server.py` (of which instances are spawned to emulate multiple servers) and a `run.sh`, which creates a FIFO special file to wire up the stdio connections and launches `client.py` connected to `rass`. `client.py` has the test assertions. Both `client.py` and `server.py` use common utils from `src/rassumfrassum/test.py`. To run all tests, use `test/run-all.sh`. ### Logging The `stderr` output of rass is useful for peeking into the conversation between all entities and understanding how the multiplexer operates. ### Options to `rass` Use `--help` to see all options. The `--delay-ms N` option delays all JSONRPC messages sent to the client by N milliseconds. Each message gets its own independent timer, so if two messages arrive at `t=0.5s` and `t=1.5s` with a 3000ms delay, they'll be dispatched at `t=3.5s` and `t=4.5s` respectively. Useful for diagnostics and testing. The `--drop-tardy` option controls an aspect of the "aggregation". If it's true and a server takes too long to respond to a request, or send a mergeworthy notification, any messages that arrive too late are simply dropped and the client sees whatever it got when the timeout expired. If it's false, the most up-to-date state of the aggregation is simply retransmitted to the client. The default is false. The `--logic-class CLASS` option specifies which routing logic class to use. The default is `LspLogic`. You can specify a simple class name (which will be looked up in the `rassumfrassum.frassum` module) or a fully qualified class name like `mymodule.MyCustomLogic`. This is useful for extending rass with custom routing behavior by subclassing `LspLogic`. The `--stream-diagnostics` and `--no-stream-diagnostics` options control whether diagnostics are streamed incrementally or aggregated before sending. When streaming is enabled (the default), clients receive `$/streamDiagnostics` notifications as each server responds. When disabled, diagnostics are aggregated and sent as standard `textDocument/publishDiagnostics` notifications. See the [Streaming Diagnostics Protocol Extension](#streaming-diagnostics-protocol-extension) section for details. ### FAQ _(...not really, noone's really asked anything yet...)_ #### Related projects? There's [lspx][lspx]! Never tried it, but some people are using it. Development started in this Eglot discussion thread: https://github.com/joaotavora/eglot/discussions/1429 There's also this defunct [lsplex][lsplex] thing by myself in C++ that went nowhere. #### Project name? I'm tired of fretting about names. Kudos if you can guess where I stole this one from. Used to be called dada, btw. #### Bugs? Probably a million. The LSP flora is hard enough to navigate, and maintaining the [Eglot][eglot] client is hard enough because of that. So this is fun and potentially useful but adds another failure point. A pretty big one at that, since of the hundreds (thousands?) of LSP servers out there, there are uncountable combinations of them, and some will definitely trip you up. #### Issue reports? Read the preceding section. If you use this and want to report something, you can start discussions or create issues at will. If you create an issue, I might just close it with a `cantmakesenseofthis` label which just means I can't make sense of it just yet. Also I have very little time for OSS these days, so this is a totally NO WARRANTY, YMMV thing. If I close your issue just like that, doesn't mean you're a bad person, so don't fret. If you can provide an easy, simple, 100% idiot-proof recipe demonstrating the bug the chances that I'll address it are slightly higher. Else, just fork this repo, this is just Python and you're probably a programmer right? #### Did I vibe code this junk? Yeah, a bit, with some heavy coaching, then I took over. The boring bits are definitely an LLM's. #### Future/roadmap? I might rewrite this in Rust or C++ if it makes sense. Having an LSP middleware opens up some possibilities for making JSON communication more efficient. ### Streaming diagnostics Rassumfrassum implements an optional experimental non-standard protocol extension for streaming diagnostics from multiple sources. Rather than having clients and users wait for aggregations, this allows receiving diagnostics incrementally as different sources of diagnostics potentially respond out-of-phase. Although the protocol is designed to serve Rass's use case (where sources == multiplexed servers) it could theoretically be reused by any server that wants to provide different types of diagnostics (warnings, errors, linter results) separately. #### Protocol flow Negotiation happens when the client advertises support by sending `$streamingDiagnostics` capability in the `initialize` request. Rassumfrassum responds with `$streamingDiagnosticsProvider` set to `true` in its capabilities. Now, consider a simple example with two servers and one file. When the client sends `textDocument/didOpen` for `file.py` at version 0, rassumfrassum forwards the notification to both servers. Let's assume the first server quickly sends a `textDocument/publishDiagnostics` notification which rassumfrassum converts to `$/streamDiagnostics` and forwards to the client. This notification includes the `uri` of the file, the `diagnostics` array, the document `version` (0), and a bonus `token` identifying the source server. The client stores these diagnostics indexed by the triplet `(version, uri, token)`. Let's also assume the second server doesn't support `textDocument/publishDiagnostics` but rather `textDocument/diagnostic` "pull" requests. Rassumfrassum sends an internal pull to it and the response is also converted to a `$/streamDiagnostics` notification, with a different token but the same `uri` and `version`. The client stores this second batch separately and updates its display by combining diagnostics from both tokens. Now the user edits the file. The client sends `textDocument/didChange` with version 1. Both servers analyze the new content and the process repeats. When each `$/streamDiagnostics` notification arrives, the client replaces the old diagnostics for that specific `(version, uri, token)` triplet. The diagnostics from the first server's version 0 are replaced by its version 1 diagnostics. Same for the second server. The `kind` field may be present with value `"unchanged"` to indicate the diagnostics for this token haven't changed. In this case the client reuses any previous diagnostics for that uri and token. A complete reference implementation can be found in [eglot.el](https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/eglot.el) in the `eglot-handle-notification` method for `$/streamDiagnostics`. [eglot]: https://github.com/joaotavora/eglot [lsp]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/ [build-status]: https://github.com/joaotavora/rassumfrassum/actions/workflows/test.yml [lspx]: https://github.com/thefrontside/lspx [lsplex]: https://github.com/joaotavora/lsplex [basedpyright]: https://github.com/detachhead/basedpyright [ty]: https://github.com/astral-sh/ty [ruff]: https://github.com/astral-sh/ruff [neovim]: https://neovim.io/ [codebook]: https://github.com/blopker/codebook [typos]: https://github.com/tekumara/typos-lsp [vue-preset]: https://github.com/joaotavora/rassumfrassum/blob/master/src/rassumfrassum/presets/vue.py [python-preset]: https://github.com/joaotavora/rassumfrassum/blob/master/src/rassumfrassum/presets/python.py [basedruff-preset]: https://github.com/joaotavora/rassumfrassum/blob/master/src/rassumfrassum/presets/basedruff.py [ts-preset]: https://github.com/joaotavora/rassumfrassum/blob/master/src/rassumfrassum/presets/ts.py [pylsp]: https://github.com/python-lsp/python-lsp-server rassumfrassum-0.3.3/doc/000077500000000000000000000000001513515052500151775ustar00rootroot00000000000000rassumfrassum-0.3.3/doc/demo.gif000066400000000000000000021356171513515052500166310ustar00rootroot00000000000000GIF89a1O llwwooo kk!gg"aa#]]$XX%QQ&II'Z(FF(')==,H---.//.22.663213R94;<4CG5438668IC97<:8==>>@A>VG>ZK?B=@MVCT_CU`DwEEEEXcEXdFYeFk_G[gG\iH]jILQI_mJJJJboJhPJr_KbqKcrL<3LdsLNoPk|QQQRSQRpSSSSpSqTTTTbMUUUUk/Ur@VuV]XXYXtY{Z~ZZZ[B2[[[`\^^^^_K?_`dhab^FceeegK=ggg£hF/hhhjoLjjjʪkkkkmQHmmnnnopp˟qqqqaqݹrI&szUssuvvvvwwwxxyqz{UB{{{|{}}}~ؗҀs냐P'ׅ^Oޏ⋌䌍玎莐gYY'\̙gGlZ[(wos[a$dhw_xJ~bg&ĸq~YʃgІio$x}o|u%毸qw%svq~yW! NETSCAPE2.0! ,O llwwooo kk!gg"aa#]]$XX%QQ&II'Z(FF(')==,H---.//.22.663213R94;<4CG5438668IC97<:8==>>@A>VG>ZK?B=@MVCT_CU`DwEEEEXcEXdFYeFk_G[gG\iH]jILQI_mJJJJboJhPJr_KbqKcrL<3LdsLNoPk|QQQRSQRpSSSSpSqTTTTbMUUUUk/Ur@VuV]XXYXtY{Z~ZZZ[B2[[`\^^^^_K?_`dhab^FceeegK=ggg£hF/hhhjoLjjjʪkkkkmQHmmnnnopp˟qqqqaqݹrI&szUssuvvvvwwwxxyqz{UB{{{|{}}}~ؗҀs냐P'ׅ^Oޏ⋌䌍玎莐gYY'\̙gGlZ[(wos[a$dhw_xJ~bg&ĸq~YʃgІio$x}o|u%毸qw%svq~yWiH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνËOӫ_Ͼ˟O*GHS:7߀fB-B)Aas-9@R ڴ`O"$ u,rp* BB $`! TC>6P@'\PB@ @4dJ81tۜ@7d"+gn@x,@ߌΙtAWfI/P$,&J 1 B0@ , 6 NP@46 ف F4M[3!a7dR Dt Lcl7P+l9A7b(m}H -@C  * 0AZ4@/ # +8CҐI4<@IXK88k"M!bʶ ׭ @dz.n  /D?GMԊ_Ab+U; Z~ 5k1>F" ;'BiP"4:TIǴ~Ƞ)~& ҐB ~m@OW5&jz9 L8ѶG[$ dH"g>q@H,K\؀F% gR81h8q)ĖwG|#Вqo(8y(ߐ6HFm'+ט3$lcݘ8x̣> IBL"F:򑐌$'tj' %7i$kNrE<*"ɈF6‘֣oHE:Rۖd&=)JS2@b a=ƙ ;Ms-D_q\ %)JYJlZ_>T6ul[€%,kCxx@+f󟪁ܷ5rr4~[o Sh> J6mjc[nVMiS>#e_uۖcT0Y!cNZFпS^Pa;:?4d7%YƏsg"pWU@ Zd !;Ez>٪4ĭնۅpkfΎ]-jDžζn{MrNv7R0=7²*\ INp0Wәp a'3әvbpS\1fFcuڸ!J9}qm EfsBוdJ Be`C0T렻L3:!xLUJÙY7ӿ G=CUXGy.Zh iU{1x_{H畡|E5A0N?poJ770ny,b>HrS} ֬BbY[Xtg]h㺵jlQ y:w'3Jxu['p Y?7 1 ~OOi$g1)8A8_r*AaI(a#wKDb0tppa 'z'K4'XXBb1ׇws6uce~cL TET.;D8QHHTofT4TsVVဪcd@ 0Owg =8Q|HxUQ'h|̈@ >xUË]t iP(IWC8ycL$$40O8Hw>5pX 1 HY'c6?z!@X$=9_"VԐt=@J h bA+7ғ?DUH t{FB^k5.IWC !^QT:&j cRA4\P0&\ E%F osYpCX@^ZJט9v9yٙ9YyZ>rQ!JيLI©8.isp/'ќVabAީ78` ')'phIaKKK8ր +؂w'{U;8.@(4hS=paq7k6@z2GKhaaK&L OɃ*zL B4 ׀iX"E 'pɟ%`a*Rc"r(*M  `-L(X4UIbTs:g:2r;&fj8I'NO$7&d^ nj beȠѨߣryZ3W ;gx'waMz0pRrHtNs$(0Pa (8%9.H4JMGtőD(XPFƐ ddze%ǺmjȺЪACᠫR=h5*z9QaR2v(\Xy1 QZaupuMS.YY(uXut ,ÉX98O z/kکcyrˑpZÆv66 a7 %CHJ+hи9ewpw8ܘgNvzʎesvƈkHKLo[P;R[TWwWw|{ũ3ب^Ѷ<۳+1l{c;"r7ڰPsKswxJhl {z#-kH˹ +$R+-5[K[ |1i*!95h%A ScSI5X{y֑F'cQe{Wxغv';Ջ47;.+yK 1{> @+y/9rE) A= >y6@Q)T4A;iO 5{´ Fy'HòFkK7jSEzv#̓-|'!0+ `(CX ),1Gk[L{`41=4|YW=]LH 3;$Fΐ:n YgxtDkYŃb9EUtE{LͷlȧlLyD|)| Վv[[gI|qHǁ9r9j)ELt v=5@EV|ʗ·|bLYE2{Yȍ xhn'ѼA0 ׼F٬5 \|M 8>.ḋh]Qqt#%\d"Tې7Q=45[UKj;OykW>s5@0\@ؗW\ 5ђWWCM@EX ŝfD &\O] Sh5I ʴ| A4DET;,dX D k7[n~ņF`DTE9\4~̷\y7iD A}iG j JռF}Td`-C OQ\1~巠t~ʼnDMeoY˔fo9gw؍R 2Agje"pV0^yaꬷư74Hw݄}+8Mzi{JA&h7.k!fs)tӄ0𾀘F`gDZƪBAbB\p8gWgu_-5#}!s`eZ4l?<4)[&ި>]uE-WzTۙutaG^p-??3lh/D>gcfş;ЀD`2{)BJ׶jѥig ;5 `[K3B AsLOwsi<P[ 7ۆ )S JD_8riP~H?ьgDcy@~$22Ne&Ȼ8bTSoVPh@*PM!LrH!Xw7Ʊ; 䘂@%}"7 KO}NP85p:EGP@=@2bRMQH_"B#?2sC4Fnvӛg`=t8vg=BKϧ7lCJ倰F-I\V )mc<$5Hx^HAt4^o6GvE4檖RGjiK%6 nsʍ-Z?B9<#nO ሟED{ hd-$_K-7A|G op0ĜHDo,"IJKLMNOPQ$R4SDTTUdVtWźP.ˋEq@\BC]5E_ cb\ b/b Balrắsү]//0;c{IǶ'Gh c-o\パr-{l |D,c`3 ;6s3) *12qR2-{H84c2|839(%<=x@ȊR .\ kџm0z:̃ɲxI H2cȉ4 8 0#`ɴdɜ| }4$4JP l <>چK d)LJXʊ̦䉼ʼ~K`O{Qh5T{USK5Y5`/N$hLM3` a"y$ݸ΄g &t `Mp$ NܠM ٔӈȀH3耬$t5MMd < Bj?(ᄟLDNPN`qO;̭k#6}3pd\ے@<* Խ87qÓ {{ǥx9`\#Tu/10} 17RP賍`RsҸ:ݭ}ɝˈ{:Ώ:r6Oѥ`KTDՄXI$24VmTXUS>ͭ ̈ bpa-z.5yY#!!X %j"D;I ƀWW(ޡ]тMۏPz !b.Y%^-EQ]S ~\H?O+]򻣥h? [;`vMʎz͈RZ pݓe"l ?J?ZZ e _ޤhg#T2$_@AR$f6Z\&\` u֦a8O~z!VhnQHfDJ%1~a]0&Fŭ%HpmwZH{*4Dcl(׭ʨ)h V *A?Χr/dM>6~ZBeZAN*BڪL[H82Y ۏ(IPeĥo8!bz) KRUf)hfc.dX)*捠BB9/B5GD1R;r8÷Ft6:C8qgZ',`QhޛT@g[DD|n(4" @fd#%i~ Rbi1ijnHЋ&6FVfvRŧ ^&:b e\JNi̯ƷFmiQ`kr;jݸkG3$+H7[H0I<,KnTHI"Ȉ3PɽbLPԵPӋ<<,֕LԮ5 \;MHM_k@RPն|7:!Q=E`.VwPy3>AQ7Ba{ JN,?cFT>oV'w59ZH &,:W)KPt uZIŋ޶+:רU@?UoEno߷lcUd]U_oރ7 z+:_ːpE<f! cL$? $ڭQxkr\ځ:ϸcc,dsW6dL$FGoG+sRgvC^盾@, t~FW-ChPtGKLMNOPQ'R7SGTWUgVwuATuY[ϋ4p&u^#8s8a\7^.d 5vq K cqRJɞKˣ,Jt.sJ(&KʫNJgCkw`K|vKv˶,wrtJfD>( 3}vS NG߬,yhlyLo NLx MN`'u OtDy%S6m R)* 9w9h< 75Ry{ ]S!7RS{{Ls<`؟\] (XkbŁ/T SUӽjUY ZU@/O}Kyj%c_}f}2YLd_@Ȅ]_{آf'وVEمm aE ,0!Ĉ nkL Y648]YPҭa^=6 E+5 y$ " qRʖgּ鐩M4PSv J.ǒ-k,ڴjײm-ܸrҭk.޼z20_8zrd<Ρ*(dkepuҐ6A  B_vy!PŌ9'׎?K>y߭+sMn:ڷs;3Q{[چE>LC-!g5N#\i4aZ7gۄ/'Y5ٰ_ EyYD9| .e_4lW2\P`~(t"> hP'Ey$I*$"4, JA@ 407/`IAS49fe`:t 5i9A- A| lPדQNY% ­gҖ]~y(d CpIpI48 { gvjw6'~ :9^J;-Z{- Nl㐧7Mb9M8ۜaP)uXo8:2)}hbο:N8M58BRR  @  ѵmZ , s7 /q7~d#ͳ 0Y=3)-]{5a=\M(eh=7u}7fl7jK7 }=!8;ܾ;9Q~ ACWuaپmDܛ:>;~;;; ?<<+<;z-sK嫿>cσ)ϴ>??@V Ƒ2|uARY5 r< . SrO,|! c< ʰ6@> {~(!ql`%& ~c"(E ;"-r^"(1f<#Ө5n|#(9ұv#=~# 70* C8@RQAZTHd$'Qj n7gq%8teNI%p.ʬt` H P` X[l)5rmc/^AMkօQCLgBX$BtH/LV "3@8^u#~~ßq-._:5Zbsi(h"Aq gn+`Uh8˙b H6t "+[PM0(VEh0PTBpGMR T (O:TT;iWwY_P 33 檮b+ 0[$E!ԨfDJxH W #Ln8G/8xli KJ @V Ôkhu40eV͊>34? ":Zj2} \,pH[얭5m)WcU4/ dm+CЋ׭\,rA:.aB7h ͊#"{F6}k#$ti&}z)Y !"nWQnEI?W4d]x^ v.EXX", 0Í< z!$ }2[fG]e΁<zH;EO0Da^8"@AFdv4p!D]qE?e.3gl 3[,)A1bXIqlߴHS}NT"QU/4ԄN3óAb9ļI1 +u1t\c(zm#=J:20o֣F˔W\y,}K`tPާ5kKd5mg!5~\}6t9,p6Ak퉨eRDp0Gx5V  '=Q 2:>ξ6DD ,*DTZb:JUʽT^#Ԟ@CUv ֚uDWz6RZ,Pzyw@ {HDaX.#)lD{"PGye3sHu0E0 NJ|Hм#2Cw"FiԱYW听!1+/}0I h-?maQ|ɳ۴9$k?a%.3C_ ܟYS FN V^ f v~ ` `   Ơ  " `! ! >!5 B!‘`f!] j~!!`a N!!a!!  "!" "".b )"#>9"$N"%V%^"&f&n"'v'~03 ߴ85P5L]*Z7I ,^̢-_ߤԵH_E&Ni-!.$](#BT/ZG0F#L4P) PCE7~c\c6nc#8# 93@/]DcD=Ѓ8A1V<;pDL#D[A}JN\(CRDʹD>8|d P|@&\()҅I2GH6IJDԀ)DY=>F?Ѓ4[\]^^f;fa&$bef[%\$@4iBfjdڥC_bfn&gҀaBg*&4&FED@@ e6DAB SeB9D7^4G6ƨC(@pieBZIXC8-DV d@RitrDaZAEX Ni.) 4FiV`) Xi`JorZBN)iB1)Fio~i)):*`BJΩijvC0N<;P3M; )BĮ꯾g|';,65ЪyE~v#- C ; E MqʑE/PCŏDT`jEi?HC#;>0p;;(U_Zz=5.8Z~F5HY4,S(︁54X0H(hlvJaGfŷ 5X6"lf,D:vK΢&ʾBD(cJjleED -ɬqǂlm^ϪlRC"-N-J$@C8jzIQßY;@݇++d;RClCrYÌCE2C(ݤ 0pb$p~9D}/D0%eB+C%D ?(%2e;Z4CdԶ㺄 )GX*.H@,ae Op*O4G DPccqD,2*6rI& * Kp!Hi%2 *;r.o&%24&v#t;1"B,s3C0 ȧcmE&p~"n`vt/\ qLe0-mn Csc՝n2B A Gn%A.[ TUA1A1Č)q2D<rk)GS/0/'GFsϮGo4Ŵ31fE3#״etJJtK_K{HϲttGK2Q H *Dl:Vku |50pD'1lG@8D }s0NL1ADz(=_ d,lC´ 93?EN~{AT+!/,#e(~ܶjk4ˮ jvIҚovk6ضCwpCv%tl#^"#`h3win oO%7ܱ;ۢU+8gz:6p6o3;p(@A8yPLvGd7|2B";ˤˉn6>0MD@!Cvqk8c?3O8DE~r\gWV2*#+鎗F"Co_ru-([S&8^8'ô#ON8 8\nMuD0yo9Ky 9ۉ7L'c^ɍ9syyH_CT?~#@7.v~3|#yu6};yzCS73:#Jh݉J;ǗLkn>dPCEQe>@tD,8[4l&3 F(!ळ@ F488*ɲ ."GL;5;r*6IsSƹ);;m+{=; -j{{Ëď99DtÇ[8TDd>$Sn[XF$UZyC #=ݗ5ҋ$^~G;$ A7Hn'GN,-l؟/~F`,&/xx8BOÈEg+VI8i^ s C+2/J½2N/(fo?w?????ǿ?׿???@8`A&TaC!F8bE1fԸcGA9dI'QTeK/aƔ9fM7qԹgO?:hQG&UiSOF:jUWfպkW_;lYgѦUm[oƕ;n]wջo_xqǑ'WysϡG>zuױg׾{w?|yѧW}{Ǘ?~}׿ P@! ,  H*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,! , H*\ȰÇ#JLHE3jȱǏC~IɓCbDɲ˗)UҀIM2gɳgL> ݙSУHOMʴiǥNJuuիb*U+ׯIT,ٳ7͢]R-۷Js-ݻQ/߿ |P0 #&xǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@[6DY${F&dGd=IORɤi%dp#t0 D$IMi(` ?2lpI_v))ei0 ), B2H`A PA~0Po&dfiџb p?!(PC d*4)iD@$hȊ  `C%p *  –ȮϫĪX B2\ бȖJz)z u@A 6ʪz'iB 4A;Z{s@[%iif h ELꚨ+h%X |2A,L q@Z|+EwIg! ,! ,! ,`}i80 *DP!AhP@-bfƎ5b8p4]@8~Ѥc0vBNQq eď8K ,XҨ7Q+))RlYc>%b@! ,ii80 *DP!AhP@-bfƎ5b$8pܨ8X.$H@Q31hWfN;{ ɒ"u=|H[~2 H 769hD-m֤iAc7+hNq3J wH! ,! ,X!t H*\ȰÇ#JHŋ3jȱǏ CIR"gJ|tZ5b"v 3\EsdO٤E9eWfe =c@k:YY$YR3c{ (?I$o'PJ)Vb%^*K:ڨ)li?ֹ)%1:$ɠ7[ DD2ⴃ13ldžC; D :c/N;Rh;է] ZDxKev0 1ހM57 .܀#/7&T@5փDn<6 UXMB& 5c2Cdp1A2L:ԜC &Oqt%7EAN5|{@ԴC)L[-3ܴs*ĭ7K;UL;/ ;PF3HZ59?&_l9bPC&&9Lc NAЏa PB b} DA\/#T5pP< ~vICfu?Qo 6TZ5Y@d8P 9̏CkE!Q<(-6J Dt ;?|x _Bc 2>1stKCX3ή $pth 퀅@q RaH .=L no2)L/W,a+nBQTWQ-q]\9;/Zۗ72k;[*׾-pW<|Z 7E~3_`txͳ*VUTr4`/2ᑢJA_~ZTovkQ |y3c$j`Md5؍7)q 5X*Y 5;` qUj B(E1CUi؆ ^@%3@~̹ (CzΫ lgY 4Wh:tz G+ueԥja ^$)b\R$Ak$&\{- $~ׁU4fCR,6%d0(XV!$srAYow~x_XW!=} =Df6o;艹X~CYc[C!T6s~75GZ#Q/N1*t=^Z)0jfAcd17nHXp;fE̐[檁Lr`}F̐_k}́(^ [z Cd߶,j5! td!'kywYZ'#Šqrwq C-7x nh?=?gApE yq8 Y/'HzM~/ M<|ڣҷ=fV`1`p<@G.NA+ _o*PkS7ŲTsd-U avX@NknPl;O3wҶ*  @dEvYVoqB:Р8_Rd fBJlGd . n .4LClWx L$.AZiOS8ZX XXTV\h&_ ahLĆ[4G1FCoPX RHiUxp;r{fe(gxnmh^sPVPj L .Š+ఊ +OuƉ@/#".3t"D`ecH1s;4!wn + @A̔V0E""1=\%J;*u m1o¨Tl#I^6 @P%T<@Wdq@U)g 1A`!Vؐq &I#YD F *9@g8 PZ>p 7K 48`:#Ք!q aT sE!ijlٖnpr9tYvyxz|ٗ~ 9 y ٘9јIpiə9時II*iٰ9u9ٛ!mq0-`2 2i! Qp0I7@00$" hh V.Y>Q"@0P)#pP02'@9   h ʟ0rP].4`Pe7``>)+(T:2 P4p *С "0A@@,4E))ݡ.ʣɏ?~Z|iZzze2:0*7ʣ-p\&"A6 w aOV$[Y?A:Zi OS%'p 1k K a\&X`eLJ +͋;<ʼniʵ*Pi**ˡzy[,먥* lA @Pn<|n`PCALP\k‡[7k 8{Z ‹T| @ *ll];6XLaީ)z* a  \2< {KܬmĠΓ Qi>QdT(򼢳/: M@˪) 30[]>L ݗ "-=&}(%,M02=4]6}.:7>1B=ԩ F}AMJԙNLRO]6=X}i\-Z`M]=a]fU- GM 0gr\ os|vjـ zk  lӬ؆ XkP k}٠MKjp kӐ؇ j3}  n סۼj Ӱۈ`v-ӬP 3ܸ I]p2- Ӑp׎ڗ`1 ӹֽ k ܍ܮ=0=޷ذ-0>@6 1 1-j`M~3 wmMӗ*kp z M ڔkrӲ} MޖP,L @ލ@@ٖ 2 ]Gf~qԐchKөm^pӬ @ l~z>{~UMކ~载O>^~阞难頎)>ꠍw~zqϮ>ײRL.黎ۗa@ @ qlQsQOp얮䜾ݞ ٜnJ l, 5:u!^:>><m> {ɷ ئP |P@ g F//-\0`Eզ?035ʼɑ*:9K缲 F/Do{ԊmJҊM.뢮^ w2 W `*׀u=̀eeZ$O-U!š@`2 @e1o3<ȅ{jOQ\O#4dO)]y;HL/_Ϊ|1N5 3P@v> <LI>> `!+c_d&1OK?<O4h 4X`B0"Ą-B 5BHHKZrdJ!MdRL5męSN=}4 bJ 7j$GGGN{Zu iK3nLh#0f)I8Zi'!5q<;H޽DGuxgy.U_ǿBIBcf~5f̣E_ s犭Gw%g~=ueТf)Q8lōG\9ϘO ;զc7Z6ٕ8wudG r$(doHI NCh}nh/q1k2Z2#s4.;., Θ*DD.M*Ц!߶N)[G2;!]@ı )J alj뭸"gZ5 0б2%l 6!*B2E CtTQ$3B2H-.@htRGل5TQGi$oE [}HYsUxz pM&T5\A/f!hl_YM$Sp挾~ᗽe nrp'旊;;˦QEM1mW|R3Ż7=w-zW$q?4<{|YT2N#%߈h>}@RLB~tR"A 695A=bC*ppFT _( V<auH8PT84޷C!(G+HD 15dz0W|E.Ċ]c8F2ьgDcոF6эoc8G:юwcƽяd 9Dd"HFĐd$%9IJd&5IN e(E9JRRLDDe*UJF~e,a DТn `E*Wҗ&&3g8bzLf6ә+ p"%C)2"7fw p @x@wNq8~A;Ƀd wXA z>ӡ]ɀW1!\t )fEpƌS _Ȋ4 \Aπ@xPphjԭ |]ƙNwBfFh7Nh2lEDy8eB \ AEЏCUHQnˌt5#&q;T7=jRڋM2uU*կ1kZ! ,l|i80 *DP!AhP@-bfƎ5bQ8p]9ZVY8Z4ѤHrH~ mЏ6qeL&5>'[)W !~7a0 RP B4CiTW uT7tBuԋѪ€ nl"?̡/LGZ>D n0YW9:`Fe& $} @= =_ĂGOb?ވgސ/^U1B#7h#nssVx|$-~ zgG(n{ѧJw砇.褗n騧ꬷ.nMj@N6P6ȷO!j;+جF\;;7^0 +kF9r0 $ AFA(_~7?cs͸p=mhH6ר.x+FX ρ̠7z GH(L W.Yr!Հh8 a1bXCý2q+%E "q  7rSxыdbhseYB$8vэ$UhЊ&"d h8# y)E>nU)LlǐD&Pl!LDH )Vb "'VU~/\,gIZ̥.w^ =!7{ bD&&"r,!])¨K2qhDF8vM1DHy)mZ qd="IJZ&IP27{҉R4c5d8{EY0L4 7b.L?p5kGmרp.Bl#҇ )# HGj'\Wəg,x۪V-쟉C )^՘w]gq_Ni_-q·&]ۓn *yÍuN)}FWBfc[az%$xwO}`!haQ;emblx[{ůQyF E>- {ƃƆ0ͨp8S1;Z$f|ʒGh0|K![gJNAڵl^ױZ(9&9QaG3!΄g8 V6/)U#ɸgȿp$Hl2Iva e,&f385c&8,k IBL"F:򑐌$'IJZd"&7Nz$(GIR:*WV62,gI²̥.GKr 04G>)Pcf:S @ p,phtcā\kݜ_ITP|- *<~s'H R ̌;@y'[)i~])vZ|ƣ2s )>jKF92U_.QDz}!P Ѐ5\\7Ԑ{n{J^a q A[2c|]4@܀Ck\LP)!$hǕ`n6d37e]0Wk(1spETE2@eN2`^)T n@Ā儸{fnh F,X( @Pn^'d`]X 0`vxJݡZs RDRs|7C5 `;% `)QpS*`@x9my%s1VC Ey[uZ8@Wpq 0h8S(j2 @S8p0"H _PlWnǀxKf4?dw }8pK% я:eqdC cpzG9TICR5 @WP4YK6:$<@yBYFٓJLٔN8yR9t OyXQI\ٕdYM^YfI!jdynkm vy$su~{} ye$9F y)#ٙY9yٚH9 EY 铽9ęI )Ź9tL9>9I lչ {)s yIkiٞjɞ9 b)it:Zz ڠ P W"a z e1nn%3I$_3 u8@T zH6I9 0ɢ.0v*Ix@ dj#R JʤN:TʣqVU&@t ro=%Ѣ P@4:\h:dEѧNMJD /LP p`ZzjʥVg YeZu#Ppd tz17QVp ~:YPV= AcȪQ ?պ Ѫ 2n jE <` p p< KJ 4Z Y @ P_>皮ƮuK%V[m7KQ 50 K h˰*+VKk A/2 8I<۱@ WcH ` kC D`X~a+c[SG Gc+VPb  :mk%n:DwxW|e_Y/ X3%Xp%WteWAզЀ A,DlH,< ÿv  _Ez NTX wgY(+a0mA0  hk gyu D  q nɓmnp C0X0˪,igⶎCP juqʩ`\ *g RH}LQieҙ̭1vt() B|H-yL{lC[ d<*k}pn mi@v-8P 8}p{p ؋ eh-M҄m1;dx ٚυ·m fhl+nj oao,}6iV;E azX@5iz2xzV}a0  t =Q&]Z bM^M<U `@ b]!%T@ LZ 04` =Pν;뽠wM;^ ^4NuRV3>A{uQ >ND/n1.6. '.AK-Mn5N8W?.T< m@ U<1suXJ,Vu * pdVcs ۡ]y5uU*V(.p(-^8Z֎NKna޽:ƷVT ppO5PT萶Q ѝYtHp\6ڋng yjY  զ4jtPWy N >މ05PZd Q) vǾ(NZ|a& gޣ|pr/ֻf_igl/n<>CK)9?_W[{O_?jf O0h0XVl[nX@Oy{GEG'o=]mig߉V8/ۍ XqY =i5@PAR84(=5jзp j kACnp75241vG5"E5[tbco4p| !B3Z48a b@. 0΂2$Q⿚7V1Ȓ'Sl-9Iq"TL)I(ULiEȤ8X}\r'v=k(ϩv٭<ŇAUIڭATlڵmƝ[n޽}݃^&N kd'Q 9&AIWoZ -M_<.D5MיpDԄ J~Cn *" 9.!6t("jI eDjNEYt[F|1ܸeCҐG,Rf|EiѪ"tb2I r&&ɏr.DRI'(o2.x(,M$tQK'gF:L"@Yo`g?&b4M "t*[2404oY/Ҹln5W]w_2l XxX33:jTo"Ƴ<򨧞q ͒x #jٽ)q& xIؙ([W;wEkGV:\x6j{Xbb0ָcsbM6QICdWf܀x Eev6,նuB!N-9s8⣟픀+j;jH&Jb~i&٘`Fx_l)$Wbc%6u=Dl?ppWpuN۸ tI'7fyGn#=uWgu_=vgvo=www>x'xG>y{ͦy_>z駧z}>{_|G?LJ^}߇?~`s@c`' `ič F`H7Hp v}DxBlm|Ald@^885"ԆР=D}9a !& quV +V`@9@XX .z1cI ,7dbD!$BV@r 2K S p`itHDtH60Hbd$:c$ 0 ԦL$BH0ґGҁ ØxLF@I 4XBP$3y S?YK Jff:@~` &Bę@. ;㉐Lǹ:]nZߜ 4`NlImG=t{|P8Ё*$ u٤ 9C6@(R~4)AN H>H`+ R@RTt6f7/ qk7oT7ڀ&BHM`1pMréP-*OK Lx j\m G čM-dJecj˝fuZtK!jmHjRJ |A2I|Hv !C-,v ]t@"dXʜb! 50A(V.%tRr0u k^ E3a0Aʓ6fNJ6Yd:F rbxҦ`)QB GFq C/Rn4ЂF PHuBl1m(V6683A< T!xɞaɻ"D !tVu`h !MfZ cpeQ3 7]Nťh;!QHudv1 (0B^yT k&`) QІyyDz vTzlF1߄`!)#dlt(7NO|GzNmzf;iOfMg,\`5./hFdow>6B'wS0 R Swڌ/ckn :ŮF+ 9 >$Κ}#?)7«,B!#b3ڐ #1F,$@14"4-2AA̪ c: BM8>5 8#F!N 5^ $ 'l{ 6<( a (6h!+ -Q*BdRj&/02wJ?.4F =[1 [CA|S­K3 Q4(DB=ٰD.3 x($֡dK?H Aȸ]:&2-ߒgl*!bFif앶Õx %M<qt<nG!FAyĝ{$xǀȁ$ȃDȄTȆtȇ$ȉĕ! ,! ,! ,! ,! ,! ,! ,! ,! , iH*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,  H*\ȰÇ#JH`@! ,! ,VH 8*THÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0+.I`̛8sɳϟ@ J(КH]ʴӧPJJՇIʵׯ`ÊK@Yͪ]˶[LZjN{Afx6`nrHxaƊKqk͝?7|w5M>F@H|ŏ3m̅NgD(yWo-CTIE !G%~Hψ8hp4D5C&-\)Ԇ~Q&Z"Yy$P.S)̈G^C歔AyS4B'%ģ|5_C5F5Xqy?4dlTScDcb94,&E 5`\s3n,Q Z衪1P3w5} Ctډg| @hb. Rc)=cbw)P IM* ꟬)멥裑NZ饙 u j(Ϯ?Z++DL;53D2 g8cL /Q?HD;8,:h/v ۝IH!:=9L@ 8ޜY,r? 8C?^ðDP8dCv4 5xC7d@ ru-6Ah'TsRs 5h' JC`#@C߁rOj 7VXp7~/`Cx6j7*V~ydŎ?n|7b HC޿h鹣K.c7<C^}[.PCgx4TgDM;8E.? ?v0y}E;"dx\jJV_ -eYٱg8f<婆`V| jV (%Txi1B C?w[#1$3rBX!pE Û'@q4HB0D`Ia7Ukp.F]5ܣC$ِ>^d IQT"=_WCHI b<@Bs $! HMHBKbR}¥ I)Y9qE*h 7ذ hЎ!{_Hc=]H 3ri?.?Cm㆖5.chVX+/L >9"+RhgxC0GP%)a 4"EX= xYN5aIBhGs=b2'#w @<|BEHuHyywr~'X5 ,Hd1d8 .tII0Fzvz0IK_e`fS'؁D4yGM!qCq79tTzF|B.BB$$Jh]sQF=p<`ds#2GF԰f~ ApH wz`X ,p뵃wa耹`:|b Q5XXm" HX HИ(?-iٸ8hD ̈no @L 1P/0 /Wz2$51ne8p1vJr3H TQ|gsHG2@V4I@"%>AiDٓYu eO!3!T@q(qIZY粕1F }XQ1UeA !i&їd)bI0fni8  ԰"Q_qBp kJ18P8)q&O `f9y D9yIћYy99ٜ9[!yؙڹٝ Yy)ꉞٞ Yym!㉟ٟy0%  Y A69'aC!py Z60 *7穖)j3*pQQe) ʝ::+5Z*C*P00ZX)J_ ] 7P6#q=acJ2&!- ,>Qf.1 j'"˦ GzJ@YIڨJ 11Jz-1:0!4djl/ah]!Q"2 m**`{# ,`lɺ%!-`* f%IyrX+Qz( Z1*4jjv(Pc!5 Jp!jZ`qP+)ڠ[A k(+*˲.+"`)2S08ˣ:{$+*j+, ${ٱֳ?{I&{ g8dfK# +p( +ڶ'[: %^ )-Ja6p,'P[Ƿ!4Э?{qJ:5{sa9{k۷ 0):z ɀA 0OH hH. P Cϐ-\* cm ?\Z.'1v0uC2g|CR\ DaM@Q:~:<%40Yt*)+ '˩ fڎ@|\<%Χ6+׺@0p^J5GQyZڣv;|*4 Z+*ۆM|P0FKj}8ڢ7\Ms, ~AP}r~5a!; 3B*ix{7AyJ-MSs u 3m %y~DM0B3 % Bs ծ2GqD+JQA/S䬴4@ZW0~q8zlƨ X \3PX䤴xFBPCNiqk\}sl-+^-jp'̭ۼzMꥲ-M q}ߧ>N. 40 jG>NlDG`Mj)* ^ݰ>lȫ .Q iP/w a$%o#<u\ F BcuBw"ptEŀN%/!0?tF?1?rFs"zb'{ wc *#A=6ąb3 nN,{!#2@T:lΜ.[n"~}[T*mL04PA_M>o!PqJQ!2p"Kj- !1;ƿDHF SB}PaQF٠R?1ĝ\aX˘mĉ!y$cʃ,]ɓ\$\Yq \<&4kzPԩ! T'X mҠjPq)>#oѯg[(ͤK:=bC(vHR Ӑ644@"|?C0|;8跃JhOP@Hhq *K27lHF*w쑆HT8@6>D < Nne7Nvd30tlzn*-r\ފL,dChJ2n,ʫ"p 6RL9#HA{PHP@ J~MJ{Ρ, o-HM+U8N(Mn1 N@jv[6.=@ faEhg-J7!JPʸ. ![8[}8"- lH""|,ÁX!IBL.xxڷ^ݮ"'m1 Uni\vڮ\SVF(r}Ye\H 'Ae; o{Niy]@6>W\6!A'YPĖZ`Cq6?\Pb_].  (a;QþPFBďԕ d_u`FEB  Ҁ}ya cزb8@Gh?$/gV 8j$F@!S<Gb>[|zA}ldLY5ജx$h8p+c(҃_%-YR!7ȰEG$'YɶIIF)EW5bJ#&YnX lr:n`JrҡJ-0&2O1Ufr%,K9+y/ XF*3RrʩI8ߢ,y@[b2$q)+ 4E̐ Y99-2|@T)@EZ h7W4ト2pLk:e`d ` B1 S\ !4MvAMSҋ;>0$Tp@MoJĆ|୸QF742PWrE"_M?|Rl+4@6t {<ڬ`=x !# i&en9q8Y-Rwz pSNY ^Qrvu !yâSh,'^b`q#ySށ7K `Q]os9" Gj65f/wގDteqd|q9SLсƴ~4Rs/ uìP lmuXQ9>DDN9mmE!x eV=4ٜ=`sz39tamYn1mH^ooAcE#y', "+$Slp~7޸v@Wx5N|oyE>r'Gyʓq/ye>s6aus?zQ>Zw GGzҕtnzԥ>uWWӱuw_;̵vgю3ik{>wCR{~w3hWzG|=Z_| j4GoMz3^=yߦW;3.Ӵ%x=})_owuϺoxomwſsnSly?2s(Od>GhK ⊌-!-Ɗ|r t7{sכy› "$$A(rSs Br@<@W1P+tp#L5>@ –i=<|B2_a1t=5nIT~Jk?TՄ6CӿC=첊dMؼ;ٌ3L$NΡ,ΖNptlN\Nϗ ! ,! ,! ,! ,! ,! ,! ,! ,! ,! , `i8A CHP!Âp(C 1&Ͽ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,mc($0ܲ1̄ڬ8缳=DN m4IF= \L#dQ?t44?W7ttm=ؑݤI-jucw-Tk-A}ImYdv߁+Aw5'7J sY͓qnњ乑?9⧏KNyA^5ѴN;ݠޓ/Եx@|^x+ /7÷^}{7Q:ꔷo;{;>ث?^B^dBӝ&'oe󎇿&p \= [+^} J>L! eA0~[!khσ Ov!o4 C&? '΂S6ARz_H鮅bA2r-,cF/kG;1^Dbh?vA_H1{L_BEN7y@2 b#-}hc!G?q\%$9EV&0yIJr@",k(K   Bݲq9DBјd3I3vk-?lŔ(5(G[F/:df1wM\⒍_<zS>8OmD!5)BH2t?ΊrۄyQĮqltʬcDoIL?Qg)YQ9Q>t:|VzB{Rrt]PI*Hmt"#*0 JK$e? Sjԍ@1M5d+TRr/ :՗R cͪT0NJNjp D*JR}Mw<.A6&)GzV/dֳ-&KH&4 &/ G:x-cC2Um'toM0z@b$AZ$0/\mV.KCA"m4TsdA!rp uq q;f Bgȅ-V1@x~qf\jyOr̙!/l()Բ؎vAd$G N {[. cV/B,яxh. Rr\"55>oս40]!HzB@C~&}Ĥ\phP#xlqk<,tM X\@rH3Of\swC@k9#H\yjוexb~e4CjFRHl@4gnL GoV@g

 : vQl9^8iDVE`Tפ 8ybCFbo4po8ʖ~@CP4 j?li:Q5Gi Ȍ<8P xK ߹5zהJKzК1y Gd*Ʈ:C9= < ~ "ȍ w2iHJgdPgYX;,FT?(\WťZWnM74p穎Jn14 ȗ*y`4g*a0 ]Yt f+v4@NYBx}' VVM Dİk 74V p(j{ ګKF <{˺4`6S4۹wS =>ypKxm\ӻ+Λ鮷x; 빵 5Lc @jk i=[Vt$sh=`⺲7^ORz>Xi5GS kmzWGn ]ô5PC p`+x,Ҝ('+\D3ZlIo,-.QrHI=5,;]=?8M 1ӝ|ʿ=#JG41 ֬\H] lM04/xCc 퐇8`;(9tJG\u֜\7 :exYV:AylA -o:r 1mëJoyy`0Mvfa{՚fІ6y==g[P8O'`UZ*_2I| `{gp g 4tLd|YyM߃Gm=E}W<~V/J 8ylFոPm .8ߒ0k挑d .߱H6.ǹ:4ľ`4 ހp VZ pz{̏8퀹s`VcH^mB zNxҜpF T(A%$l*,}039B}Vu%\uGĜzz\}<4 {- p5qAk@@ IZ.5NMFd m n`5 mƊh~ 8ym'5>Վ^ .C. lԸ릐4em \ 4ȋ^ nNna>5~!}?n]Lް3X (46O ؐ=65V;ȭ:7u_@_MhݤYLRjh4my7؆Z g5^ް׾m>I}6 <@tJcZtJiYhO[stx%q K<֯c?C7_`@ͦU TL =<8 ,8cN k q? DPB ;DD)Q# bpcȎ8 $ʔ-]Ipe̊ [2iw !,tTСl4fq~pTUƤUrVXe͞EVZmݾ ӫѸuśW^}v FXbƍ?Ydʕ-_ƜYfΝ=ZhҥMFZj֭][lڵmƝ[n޽}\pōG\r͝?]zvO^xF~'lӼݿ?l߿0ӎ=;P@0S$"0B 7䰹 EjCODGDHD [,1Eg _G \P!D2I% ! ,! ,"V H*\p #Jh!D3jȱǏ CIɓ(S\ɲ˗0cTQL 8P`A6j!ѣH*]ʴӧPJ'B>#ʵׯ`ÊKكP1ERD(@ AC%Pd(@ oX ]KPÆ 8˹ϠCM#QQ >` EP@ܢ@ 4Сm΃<1KKNس/\¿a !bN@Vg5]}vh&`IYx=`@ @P$wA?[ Tc ,Y ?D8?)H 0 4^ЇWP$/6PF)%G1ږuX?!!Y?'Nl&UX](t@'{A"AGxo&袌6 UW2؆jU[ mi @)<`  Y:*무2P@99pe fE6 ?q7` @h+J-覫vdn+K,l' 7G,Wlgw ,$lȠz, 4l8,xX_8V@~,<-:74ix?6tp@?H Ld?L{Ww-#%t.gB `<$z#Wpm^f@[.]@99M{vtCYeo}./wgn = ڇ/#l㧯/o HL: v`7h.(Zծm{OLBv kY[ZHC6t3~/Q~cH#:~yhg;=svX H2hL6pH:x̣> IBLow A*H) !bFH*@Q$լRk@2PTޒDG%ŵEG){ F]\ %$nj&`5S'n6?3R  LpnL皺< ހdIOYI̧cBOZЂMBІ:D'JъZͨF7Q%gG;JRHKQ01*Vh'  Fev6ũNyyXёU!ZUfh=A FГc%t`8J:>U7j=PCPp@J Q ?**P^zph!!AIxLSݎ$KTkg=5 K-zޞVI[m b[l1je $%Ph[IN Et'P 8Qhk?!@TTJ BzB9AC^&)zrTaFV aUmn T |poDG, GH^NOJN[R|X /C OHN&;PL*[Xβ.{`@&Y_!JJ1 |.B$DS` TQ#;Y!pٔ!uv#fTB)A@b dGYc4QMs:nRimAKCP# 4_ss&g ȄΣ?Ai$3)p[ mn}vFPgPhnS ZQfZJVzդix\6װ5}kW&JN$YVm2#faqB+t0J1@ʐ `c Hqp`$}MHDBu(Q9Q?)+u %K턵l ==wHb8W&HH=M 57A ouG 9Wx Gvk" A@ZatA 8_v7nZ=!ek*Ĵ}ht. RHB/S '!S?2W>ִ "q݀w}C7y5Aޏ[SK$[IA K4iI ~oS|рH ptphmHm#8݇:y~ ze4}"ۥZ'=({ޕ9ELc] Mq~f [0Rx!TxjeWd+x-xhe,,q*c^qP_#%DwUW|~VJ wu- Pu uq~҈r"0r7pYaa߁`*F`_`1Cr*AF46vNXEYbW o@hO@(H0167M }(pnhHu,Ȋuar"< PYQ**bJBbȂc d"d-Ҳcc<Fc`cE0gЍgP7clXBI` ugdgf ] JIpL邌pt ɕ)(Ng =n}f XSYǓ>YtYvyxz|ٗ~9Yy(m Xڳf &a 6'Bk (Yr..j>IJw{A1"4<]d4pdg G0m ! y p p hP S Y-&nKtnVnhm&o.#xda)Շ56T20Y: QPBP i`%@s9 E  G k@ џs>tsEG|OiQR^Jq#ף* 7dA F!w,﹢w@УY p*Fy5Wzy1Tʠ6`KY%΅$ѣ2qb:f*G Y0: !@bZl]|G}և}Ǜ.+$&M: Yg !9UzoJv**F )с8&Hz˔-Jb/E˺9pz0%zj0j/z]ڪڭPqjPWWH<WJ*i:יXrJjz Q *!Fxq1$.%*1N* P))@5+? *XPI{:P; +Z + 6p(V؎ؖF戎xSbO,rTԂ++>;"`gq`g E P py -:Y x*Fk`w[ ~FW9E~֕q.7"w! +5`k2O۴gԾ[#P@Pa/B+<\| "<$\&|(*,.02<4\6|8:<>@BnwZu:k 6@ n1 ɸ08WXv~w!]!Ȏ<ۭUD7;:JApQZB:<@kT G3-51Y {·{3$+X,m LZuGK(z׵!a 1bNj ,+Aqb9<Ԓ^\E`Ȉjbp R_mA %q:@jt} =(ڣ]82qbF9Hx2+TjդYmQ#],=XʭlX [qݰ],~H5h}M^4}f!سѐCh:@A{}M'hNV<Ϙ!pѡ@޽M|oK}kq?KBJzP7 *:U@ ᚧ +  F.Hަ$1>F2NH<!O}YȻ2 @}lk x+Y Q`\DŽxKk_(J>#A㚞@Fp 4m^F^~븞뺾u2wM a- ^ >P 7@7^ .~`4zPٞ~>P  !N.<3vkknN` >.qX>o .p]EP Pb7uW,ЯR #?, p]N*_+O{.P@NBN>PPB?r6}m^o[_h:@@l[r6 >s @oYP. #JVO0t(gcd>qHPP , r6w?K7 P!n;0_`/+Ò$c(֒)eFP ^>0UE0~S>gQ _?ܓ=3S>>b>_O@ DPB >QD-^ĘQF=~RH%MDRJ-]SL5męSN=}Ԡh8$jTRM>UT3oXj֪8װ jHZmݾ7$Y6.ZW^}8P 48qt `-FxoF]LbtCAM!8LZd U&@`NtIV`6w 6*Ճ@ѤM HuHun{]e̚qztөW.3u!lٴm_wk78s0@*a0:@ BPA@n>d82B <̉Vt( ke.hI S8D`رE$Qg>|!Y!ϝ9 X Z9 :؈'>[E&\(%iyA[޷j_&0 l +.HZPamZx Bh;VmBHL$/\ 妛hVffm"g{g{'IycX=8GsSrm):xJPl`+=>'V2eiK_e3!>%h|qNy!|o87(nG=Z'b>~^:ρ ^@ Y!3.MZI,qW+ i8bR`#JTnїt40dgeH>\n^\efPR>T^lW>gGfedY^ft&fu vpXjd@u"Hqht0 h`h_j@`vXthpuVn>%&"P\]@p u``_0oognqthFLhp^( 5!(=8"(PUPN/ * _QX|ՃME6!/ BHlϾUP%?l=﵎#džlɦllm6 mFmfmPp6WK!2IM2bdL n GnXF(dj8MdЄL0Gp$rs!n+lWo47:Moes&Op oA@0 `X\xqq qqqgwppVr $po./q'g v o hhjx>v Sȅ8sv`nhk>G\hQdhwv~h5vݲ୆irfS~ZWr@&:a(w^k [WbMXBv䞃BXM .ԆJX6990n^P/JlW^# E 9^E}nvpW}q/9wyb{w?*w'8*Vxn:La&@ k \[y7P؞Xvk`d4%0XQ88'e`C!kfWO]hzx'@y:fs{G|_|iɯ@tk+hXr8zuh 8vp>h *z \E*&R~J-%q\`AXL!x>]:FN1jůl:*[($xE 2t 8Dg4?RcHQ$8>ܚ&߃ kFpCX#ac 1[<5~ ߱% Dx4Eho. x>1Lb"x(Vqf"pI#p tlvB5պ51 xPWVzUE idr)/y[['A#8m0!eBGc~C(%Zn a-!KBsݰJ:*y7Ĭl׉/`"j_;ۣ0_B]0lh{ƶadmx{wDfTȨ1Kcm6)ߩ z2C&&g`&͏pugA 2<]pi@GvQ"r.Xق]πuX$C&G9V^;LDq~bAÏ1]I@c .HoW̋G}I9,upANv7G|^lŠ,&@cG1Gx#qD:zDa3qs\v _|_wc"}^]xqx㩗vF㢦d8OmC`g^(>uBԁ:MxD!sij?iQc=] Ja->(Mڶ)rocn5e_&_A-v?x@fDY}:xT8?$yZ A5灜Aj abPA[ B&.aA !>RLB;b7f@lCG8DT4!f\?kED!Or Hv "!."_#2$.!$N"ȕC1 5T 0}YBcg( 5ԕR5J6 6n`C:C28/d7ڀ #=."c=>a pC!J"4>c>@$AA$B&B.6c"2$DFDN$EVE^$FBC"bFvG~$HH$II$JJ$KK$LƤL$M֤M$NN$OO$PP%Q(8$ N/%TFeL23A*bjeEcH2."Aa0v BbVzeAc0T$[A>%|Y2?%H=FU*Y^AZAaV"C %0>fd b:$6&Ar&b:gb36#_ 渄 *cܗWj8)%^&DL  <-X2"W# i2!o>[VKD1z&0$pCu%oG݋7gig^6f5M  37jWj.^&7-|1njY*B\FtƝFZ^CxFj*jl0C 3ɠgtD@PLx/B1zC8r1l/뽘+5BpC1ԪA؅gȑkõ\ =C+`tA  ^ȑSef:D]jë!nY%7hvD>jr &c̎lPlY}ܩZ*5`e,8kVnZ-btCl1j/x- Q̪j,-\̫fc}2IئtBJ@ueb~b'B*H5TCtƤzQ9`M4C-~.n Aa@5(3 /,o?# /H/3h+f[g-{T8#E3AjoNyo㩞"r)`@a[TҀ(/ p(x A5lk)z5ؓ¹B5`Ig)Vr CC| f4oBMZSCסB1E!|,8*,X; N: +?R1 Ϟ:w]WWl?pA/`oqh@>oW1ߋ#Ij0 fqGCiȉ3 @aX0#/n# |jr[ ыM)dx &90Ak 9w. |i__&+6.|a;xWNs5_Si¿[2ϓfg:0+tș9ye< 2e|D@:b5HePK5,=B3=ߋ=&b  ;G4Z:EӓSrD3p A|N^w:r* Q+ 88.9pEDFSwuA|?4u9:4Z; 7A4JqN9%H?P_@8/[ol@Cou (`,~LGv5I?uN+"} |_vX#)^vn0b+iDc8:1!IW6.WtP+Q44:9FbӅE*Dx /bsnj !q?bqugdI3 xw%;773x{Fy(>o5=A1\ؤ!Scx.{SBx7nTu2hV8`Dy@DrK 70=Iۥ xg^fF.HZ8nԜZIPw+Dhvm:@buD:dS`q5@yw[I\1]G甇^/I4*3n7HbFL?h2Qnp4gNQ=͛i 7B?yO7<Ӯ9ZhxI)}^CA;99\qDxex#ng/j Q7CDw7swx?eX83xD=W8gze%Dk+W&؞O'x)mT@,@Z/9t |Otg4?xg8͠45j8C0M0G9&d(9o_ɕ?q?'w紱BHvo@{M} V8d yD?o;?kaWg<6 b<ͳۼ`!y;2Fcf{".g.he4:eB:fw45;Dsk۷=0%9~y[+(ݹw%L\+xV~yiD0>{)-o:IuOjD~n6Hgx w,+0'><>s!l(<}69C=@߳< Oԩyhp_lcǎDufe< #עyu@zcpR.iwЗh:?2ȁ4NnUҥVZ,z3N=$h1B[ ZV.*ETPznPjd@V$msiz#!k]Ƣ\5Bhi֪&RsJ {(ǺrA (ǩK~q_*PfRKqI8 PÇp& 6ۀ ݎJj;L$dB,:C.("}3+sj"+4?>'L.i G490-$~EV gu np VQ rܢtuC4m4 $lٖbʢmhxydow~Igd큙z*$oX=6YBk:Aƴ9si[~yo 7jbIje~_~$Bjˠ'bQk{gI/BTߤ-H5ao#_(S&Wށh%fֱaɘJuJge}v0ڙ5bYh9h"#=ꁜ@jRd%<= к酑!Փ%2)Ev^\33^}\a#謘vL[ƺhg~ -UGE4Go/Dq  ,Csy5( 9`QF+[\툒'}K Sjp o!Oq-bbiȘ@ rа_X'XVZCd;Bxco86?_!$CrxCqZ:lE2hmu:ȁ[PV1 FVx`%rr 8Ǒd\1Ckpj(4Qzs!J,gEq!Q> LHn葏c C&)e B$HѝRG Q 3i@r$`$Ӥ ASRIS8A=]ʧ\ӁD71 d'77P>DIX3u# +$I*Sty_GgEL5]M[_Y&v*\afp`$aCXB6T`rδ_[*}FR* ⵶WE[6$9jS* nkc.*nsUZ\7AtZ"t5Λ<((u{_Η}_`6`/ v!a O1a oAb%6Qb-vacϘ5%\^=e[tc%/MpX`-=1!W{d1e&Վj AOafܹ_1 3 e08~!gBOpB")|@L4%t`{GRQ8ȱCsմ@XhIZ؈(,i, qk^ؾ+v}hlײJ&ᆲ_y6b+W+ =@u\s!+TPH]!eZ#e_<18-Rb Ȉ H1C# Vhg@ytF=\&7IWk6q-Arݬ'eS]6 Ol#Z+&DSYlet8=Py`P5u b9H "Zڻr!@!W1I5*CTk:Nrx(y!a7쑴4s4yxceEv@sؾGr=y{wTw] v丆 X@ĘJ^I)JL yDMJp!$.*!N,ʤJ B.xʏMgZuK9Xظc6H^R:PvV^Uܝ{ܑ&ƄPU A|f4h4v\`А8z.w> Fg@V45@@ 4_D@ŇDkNY|E\4P^w>YV4F 3s@E3oG:j`S9@GeGFxdH lxH;xF;jP7l>U Y8dAyJ{RqOz{( p hV` 6`` >(afO4t衇(J}h"(M&~0HR h8:Dc9裏;DdH&J6dL>)EIXge\vyݖ^)p`i晰lfp؛ri_tީr矀gՠ&R.>5 餔$ifzҥv駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧`033C^3O b;  L<83P:x#7޼ƒhr= 35|kF5:3~J)38naL:'EA4a r'ɄRT!-@f*90C0Dž0$ wCMG Ğ4q! \891AL@?`2QbI/zO\SXm,/wl ~  B~<q8G# Ab0:Ē! )$L_ʼnhT"?yP6$}CZpxx4AWrAT=PFV6ĕAa#uFcNq&H(͍T$դf.MR5ZYL@@yL3A@"MF3}$+iǗS 1A=CQ$k$eЁTx\e-Q2RyļjB%$-BQ#>l=!ώ4*EKG44"\bNB(@>M?dPG@ @0 <``F:%9N|7xL J@ i0vRPm B B& ΎVE{dZmUR\Po*8`MR1%ovI2թIjZաbZ%H j8BvLlPXfV }# +KCCUaf9ֆpI~5})dVвLgHڍĴM-b;m}JCPB ע4 /.-VXEYY*8% kDG-k*jħ=YkvG n"bLImsy;sECt T[Ёz5AZ(wjcBNqܣHHy9>-:s]}̜ w:9Tڧ18g>p|!1͵S޼TiϷ c|IL=1BXQMj/aF)) ݵRRڀ.kig;]3=y:,Oru'ţحg5' m#l9 G]$5 d7{ߵJkhGW>F>{ ~y][$ r^φ.!aER]~|x?I`H"C{4sVdkm yxߡ] uL'W{t%VW[|XlK6 e*Df}'}i}BWW`nx{\7uOWpEVO*s,s-|4Od)'(6t4x<(s2KPdjC?@fgE2xGHCwt'T{NgSgB:HхE_d~UQĆkXHKygvKhGׄ&ogSfU8m4\ΗmsX}0SteP6G]XCxqXj}XN%SxYe^QG8rgd|!j$VEw6]NXDzeTR qHq[ ENAwA،gfAN8;G'M6(FW%tx"s@H8Ń99Bd7pW4E4{4\4[{EjmG%G_|UEC6)]|68{!9fE 9d8s莟$Ri}]H IhaYGM@ eEgZBh'ZhtJsD KFvQo&C&`&☴ey6~T}ױ%ʵi0GY!KP(joJ˓;=JbPeKRHmkFP볅h"DItgudE&[oi{gTj}5j]XZ4i YYn`8iEfx†Ug/̐{G3kl?0>H]HFhGs}7m @.܅..1F^ !it0 ?MӖ $e= m4P5LE=> 诐`mP{O>9 պ`GNdMp= מ@>wFmAUֻW§BE?}53 Sgj- ^_sd.ѭ;5]ԏ@|ĝf ~LNӖ>8s]~?- .mԪA Kdɔֽ.}ԱCM50a-Jt @|\}#}}t܊;6` Bec m -s_0r+q 5`H85`{=L>//E-GmٜEm 6p|>/ /<ֆdܻ/Vʮ QWOw- >>E-9d"O@: B.і?4V4~j`F#I4x_#6$0Tm!s͖C) YC%T?yBoXE{H_aųcxbHGkƢ(|-RJ,ڤ`C~h׏EE'ҡ7 +EAjHY 1ȓ+(6c\Ehrwv*m!4ij0P"*%2#?(Ĥ>*ˠpR *܂ .D./0+iBŌV4H)[lqjpε-J=kʢP 3Ұ$?gID3M5dM7߄T*HK2+봓x3I G*$ H ~HX3:"O]-BҏD)KR==ʶr1l4-l~hJ.i\ ׳XR Eˈ10jY;Ou*M=ˆΜ GohTլeIl36N{7_}sHnqs0ၣ*2;R  d:MB: tȒ{T Ұa^N,wU@k p|͈ء)#V *!l¡Ұ R6₲8ꚥl.jumnlW?l,iE26&T:GQVͪ|u8Ac0<"UTx"S2TrRv6iѣH*]ʴS=}JիXj2!qfk?"ޮ]FMS @8%ڢZռEanNʸǐ#;Z7X<6Q4Z7\q 5n&+p&oKnbx% N)fl`&5 q K kjᶈ:m@`y˟OS:D@E $#K51s?ӨS Yװc۸sF] {ȓ+ǫУKxIhν${۳ӫ_`_~cϿ'`&Y.F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wܟo2(N=rM51 _sACGC8w83I b̘\>ct\CxGKMO,B@Dp)tfdqRמ/@4NUZ#PSMCQv%7PtۍP~9 :4  AmQO=15BCCBMFm[C~rT7щz0AC7Lx$|AC0#803BdTG:D5B-:I }~\y$cP.eS)D>yA9AyG=!@)Їb t@j@Qm6 *^X"P;/np1x# Ez 2LD,T,S[B!5[~+lF܀<@ lqkM!W f7-+^qz^ Wr4M4Qg l1N x=8OJNsh5f̃.q$a; _\t f :_>g\5VPޖ$9:ƕ^Hi ds3+,12$&e*Ab&VA9u7fPUl %]+]Us(VӁLT H@/t=" #Lq`k])]jEVWֹ!}`A",l.~fQ8$rq捸{ 8XnA{/+ҠMsY_j%U{q n@p0  1m*QD/pRBn9Bpb ./H~@\̤ūohm/ R<b8:ivB @}@\L nC;vV1d;F2'$CgpCĸhw3`|-G!{?@< )4KdB-iUd9VTT@Z`Ֆ*CjdG@9ɂ}o^ (daχ}|C_h[lʮbw~P4EVsq< 0[Q A8npD[#t4wHyTbhKCpO75G Ё3Xҕ(Րe!$G/8Pp>]CHr2846hr8L:Iv`K8?M?X`fSփ\F(= wQN7 n7r7NTLarw,3Dn75C3 \ Kp\fF9 M6 Q3za0 f zzDr$_@issl2p~{'YG5E{>cTVq_Y@^iب~eW5BE&vUs!Wٸq5 xQDiOcv+3=T HsD5T@ vS KV ّpcȂWGpy9! ɑ2Id7yE#]+iՀÓђ 3)eu7I'iĒ0ɕ$v8IdFh< 퐖iyD@ IvMiӖoN@wЇVhtTfp\e#QDe_? e(0y!zPCPhA 6{ > >{6;Q{&$@Ռ Q;vUzpXic˶F`B5yWșnr&dAQP !5>3t$ d?i'H9iiU!Q***GVcQ]@y p*aXەG)I j a7Zh8ODcT 4@*54w{T}gvQ y)x@8I AG# ?9{}RQhhdCHmQ9@Ts%|>{|Ì| AasSg|FP 46ʜB)khjʩlٜ:nqy׵o`Хbi*4PH54d N:*00*-x J劭ʭ*:qSSq!J94g?:Rj?z`RjjʰZI759ZyNG f )\`_ܰz NT5A. ?D:vhyy'{&R0=ggxzhz|JAĚQ{X: 8TAi;'UBPzq~Y3v;tk;P|'FZmk5u{y|8yZ  'cZg W *7wL`Q @O` H3=` [QSFuZdb*ˋ3n`Sh{4K+^[8+ۢ;[;ؼϻڽ8N h ހp 4[lU`Fdd}8~Y  havyY*vxSpYqF$ئD{4@ a  h ?77:kS!il O%T6CSvUVX c kh-iVj8W6TDDvY@~;ѹ !ƛƫsiy|njlǸgj},; 䙞  & d [ (Rz+H2켹2u\`˧p\S2 Ћ[,˴L`\ ̽G,+РʶutI\qŬ=\ \ ;l <jq|5F)*⼿B@Q1аT ˤ =MOdM08O& ţqA6B  40rm12>4^6~ :;@<.B=%PpP@aX KK-]:*1[j>2`+q60h"6{>xd *km( ,}%'O ~^p!j0#-p A+[!M2@,.(^7P}n'>Pn`bn# .nq@-^-Mn'p-~K ?^.OQ"0`NÎ!PQN w a +RQ?P!T?VZ0P/%/oܾ]o?_XZ+ nc2Q*OpPy?o5ajn7[.^`?"pm yOBo>#p Ÿ`"34pɾV .]oQWן9ȯ8co40/750) 1k !Dw J@Oi( A 9HE4NO ~XQ濏[l@XȈgJS3?φ"( '6tC #$HZQ翛2tp X`@a h&`# 6v,O"4 $sEDg~aoZ64 e80ݽRye˗]jY7B2Woƍ_,!rIm6~O^4nxW% 4lm--3̭?͖_+gvl J D( 1Ы$Bȅ \h-R"JDB8*/0>H.I !aA:AF *i& RePF~Kǎɪ@E @mоPV@(h˜:4RU2U0CK,4C)AD774ĭSS]5HO% >US-@/>e4]DgZ\qפH!*V{'PTUeU~c'm1%8 G 1d+a2EF9]YCJ B rd+K `(RlV*8EF9c9,+!Q6+?@\R>s-z/e@|:"sJD ,g5g$*,1Fa6LmYΙ]Xn>=K5]- S=}7/\|zώ~>wn<~{E%xҚF@عN}~" xZc$ Ue zHIl=2@Lh)Sђ ? @jItc5ID:9ƣ ATJPLW:ǰtjh""T$.JL9I9J> t5@_QP"qoT B@Ulae*t2wEaqrj@ XxɊ"Gjo?N`@tt| [M(GNO[qdR2L`AYfEb9KUDg2a@E? O 5jA:"̕ Ymf`/4 O?4R-($"&RnPTƁhm' ~hqq3Q2ev! K&ɍݦ7vpX& #ĕ4q-v.fcQ/뭁X5(us3-F( Z% HehDNH楩XaSRHi!3 BI+aLt-Z<ť|bb U&cS`R@^8ΦWgA6n HUkҹMY5:zxKop \b\x3PjRfGiKg!v^m@ "uQYPIP .$xB%y7΁A[KP (<ĸY? 4`Xì<T 0 9(@ 0I pA ȴ~ 5 p蓴ӤS">V#6{A? ̀0`Ɣ˥tFəɚFrɜG| DɈEʡ$ʢ4ʣDʤTʥdJ ʦʨʩʪʫJ|ʬʮʯ˰˱"SD˴T˵d˶t˷˸˹˺˻˼˽˾˿K4JTdtDŽȔɤTK̔J$M5Dͣ,dtׄԤڬI\l`ɼfX$δ4FܑCNTcN5(JfjijS|ΤOf}X=5bIL2HR=8KO5- /0uN%HI3T#:@7䬨?Eկ?~Ɯk۩'K4ճ@=T%ҽdKF Z ۱ SNuP%ȿ[H%%ؘ5R ]_8}a^5`%a\mW-\CUaePv0^$\GP_⵼%Aet_.^KobL^xc{pElwuPkogYB$V~Oc^xlfe?umFhkaT[RUNdV]JgI׾miOXqMnguGο8UPl\w|$j5uQnj%WFk=fanP-VJ㜅OKЗunfQSl{wP^vlXEZ Yo_֭KWW~h{EݤmWYlwv?{Olďލ{DE{ ]PnxR'S7_y[yTyMo~P&>~yQLzŌ~WGs7Kԇ|b\Mlej,h „5S!Ĉ'RhDor0r#H.F͚r$C%̘2ga&̛4wμgM:-j(ҤJ.)T?di*֬ZrmeװbBYRm붧ӷrҭk[t/.</Ċ3nRÒ'Sl2̐7s3řG.m4ѡWn5زgӮm6ܺw7‡/n8ʗ3o9ҧSn:ڷs_,FwÓ/oǣ;tïoundW,_v~}x'ׁ* RϠ#P(#)0Uxa/B(40~.!:b3mѰ!Ap-!3(N?}'SD #p1He\S"eR H y) L.-DY!pʄutLN$ D 6Rc[wIzeL&arASb:&O稏#VjyhDF55M 83N8-  D)sOhr4Š7yzIL^q9XḢ5b!7ENM, .ںf5x3 /z+M1?/3x3uK 0>vnp".@|o PBV9_Ni\>JlM{*eUG{jV? & JnM$ VJdP@M8A4rͶCB 4΄߷/8x`ٷPP3m+BWS88RL@\sKs#J 4 {:첫O3!a8. țf^-eWF ]?C[So6)o}t M./dD$]D:!0v}nPWVsAsIjq{ !܄5?ui-44xaa X1|M6"C \7<hBT>WI<lEjJd4cJrРpI.w*Z@?" dF͑&u?O+% SKOn{dXAK*GƵOt$!xBX o17Rr*@T4%ʡ8p窕4oypI F8smMLW&1cEaF&q5иo Dr*Uqxc|vh7@c4KF720k_Јc.4A \ϑ?[+ժҦ t,)MTN*`EX$sTAt7p@,WU00jPэvgdKԹT3ceRY\ Fz5ggYdX%dN&<@GhOV*A{܄^uP$(kY~JvK0yI>-/[j;[G-gUV;bM"~K+WO-KB ĐUÚ5dK1< &2P|5hRҹ&B*FQp5\5V^F*Up?!}`pDz5(ac[ 2̓TpҦ0Sxk^ҔZ*qKit&  tgơ Ay\ȧ"<p PdMd!0aEҦN_l8[tT n :0noZMw,6)kvA}..gN}:>c. ?vۺ0g  d$`q_gGF:_^ fn v~  Ơ ֠  ޟ!t!!!!F!bFXbTXVJ!8HQHμvJ@ d$YxXb2"b5ĚO KH"%L|Z^ "HQa]a"c!6F,.6"xM#XL!$T?y $q5tK.7\/L?$˲.S;L(L7? ]aD׼C|H2~LȨ f<Ԫ} nIP+V}C+Ң"qUr9]H$ =!}p O1BUB N.A@5̘L `UQPth=O:Y \X(_NQ \eA˕嫽%$MZ[ Fjd.B|a=DRL O  #DG 0>߱0QK8R  !V-?ԑMY0$ΑxD"!aXZ!! ~iZ $G\nԤ@*!rJ&Mrb^֒8W}ڈNxO A#1鄑AfQ/LSBB(X7q AAz6VZEa)f(d~ZMDQY:$&etjjT*F(Dn-EZlub:8$6Z-IĚ¼ʊ|4((vx!R;XX$X9`]Jzɾ!=*?f-*ٞp.:%')Z2'^lN8[? )l-* ұT0dOPŚb bRo"hHo+.\xvnF/?TH%or*"^x0 0WRg ! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,]l iH*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,]l  H*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,]l iH*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,9 H*\ȰÇ#Jx0ŋ)jȱǏ C)I(S\ɲː&1I͛8̘ϟ@2iQѣH$MӧP&d*ի@ׯ`nٳr=۷ԒKUlݛLS. E̸HMKQ)>seA}dСȟSHukϭ_v{mõoޛ{o Nȓ+_μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨ̉B9T5∃$58y(Hɓ+A4"!X4DGî6P҆a+.23E뫴e;1Z+4M Aizr"F Ht8蓟D9L^25As 4_0jQu(Hu,/ccA"IJ3z$,Ɇ |=ˇ܂ܒ\I6WlàQmjJ]1plNIѯfҏ Fa/b\@ +sb1{"V,cC^*8+πi+; @~)wV,pkIert+HZVvc%_BwH= 눩!H2yk^*aX+27A0?I]& (r%[D]Q _}ipmz chU_Zns%YGp =~PT+6lR@Є8 ) ,/x  3vCWpZy6W1Ty$3j1~Ѭbq qX1q-+-surh@Z{}Vb0 lkP@l/LK6g c4E'; _?, QAAwCBR}5\cXH֬ʵw^5G=#O+Ғ_BQ%^aKljrtki.~5\ik.]B"q42a{n?3glkĤCwOb^i{"_;M>kސG-%~ďHaq~ކyl Mن#G6qPs\--[.v2qh]v2X'{׾c[ 7o|9,GQ{! ,C H*\ȰÇ#JHE42^ȱǏ CIɓ(S\ɲ˗0cHC͛8sɳϟiJCp`Ǝ4E jիXjʑ*ׯ:L8ŧDz˶۷p:E+.&kW;5i`ᴦ.N:c Ru8'Ƹv*]9 90ڧI˺װGÞ]Y0D{aA#Q" F#N7xE8qqh==^#ѡi_Ͼ}L䔩 \_ , @6 ?Dw5$jwuxwh(:ae $@7#E-Xh@8c%ٕ$~hxY%TVie+^ɚ1"dd`4`F Ev̘d>x G⒦y*(XYA҇bF`hf  `0)v*yrjAj詨zSr?"/GtEGisGy2ըPHgF+NUиMq@p[ÂI0k-o^&,miB艗~GK)yodC#&5PXP70M簥P|v~ ,l9.l8 ej@-ЮXr[[70VWa'!=lZ;b;{#{iؾ2]VcW'|D*Uh^~(p$ $] ^rkf(;`z,N̤k72oS-{$۞_ti|'b3Ur}fgWdnrJ dw4v WhuI,jѯp[GտܟD6]`J:׻uS`zG&jɐkkWzeqi)ҹpJ]([ԙ1=nSiriڿ\r75 pi[[_絛 pSZ9'ccJrg"6Yl]kNiNeQz]uqOS6 8Hc]գ3Mss\NaZ'V}s]'j'.}`"#MmtB h|;]"%9~iقL{nne%`tq 60@ -P@+^E"8"@1 If&*D Y %)60Z(ɑٙ870`)ѝrC(kFIf05I)P @ N Q9kr'uWggdu隫Wml9^%qVfc&ux'Wyq:V~z&#vt%t99pqp1@POiX*ZJez(~!^z4Pa*,0!lʧ94R ;0]Ј`5a* :]0l y79l *p@$pSPQ]0ڛ/zz*fJF*G`m|0*{-ʬ]mٛ=JxSaf r|jW(q9(4 @2&ch4P e{Xr5Qz 뮶xP]U7@ ܁1.I WK *`a jX~Zi`Z@ =RP(;+[x"ڌ6Y?r(OFC&R;]zv.;p9[vh~挍)\ebA)#J'Ux;{* [۷۰ktriAرI`(;I @۩|p2 =i 9k,Y @biS!Zl>i)wjkǘcږ+NF`Ft GqRKH: Z/T9np<t^v~x琷|~>^~芾肾As>t~阞难A餮~ꨞꪾꮎ^>~븞뺾Ή N>^~Ȯo>>NھnS^~B!Ύ4.?4N w?u^"?$y%*O'+0?-1_637<9o=B?CHE/INKO?TQUZq^`b?'> ZP @ 7a p 4w! @Jls 0 gi.\p*C y?1 aHtrs foԒ nP/0[IOqcߌ@ P oüՀ$п[!{Tǟ@!0 0 Dy  OoyP0t @O@B PbC=VϪ=~ =.!RJ4$HEqcLC'ِd :4́4hD4TT2F&V}+T"hӧl=W"V\M{^$ckyᯃ}Nz0z(ɈÐ~t>01 @ U'DPlrcGǵzYhF/Y5ehdX ',j5<5E^t+4jTu37ay*4x=2ȅkO SM <0Ajl0>@hB#l;b1,+B^4R "At0oH J.CLcD<JXvlgjrgdY""qAs>⡝6mvAp`uyZTd*uO_oF9dgp&_t2jP'Z< kIk4N5(ĤMA" ŢU*!VNG?L pS"e0HM@Qv $JBLrba?R#B,ŏ#( >8u.'xc:DNeij4 oȨMj&FHՁ! ! :{SpA0YGnhVr@f< NcEBVۻz;no鶮<0 ?\*+ sqc %if{#sA@!< D ! 9Dz! zì Dpe`Juρ䢬+I@ I26hXRJd16X!bl_H{!d*8$ FHg' ZOZ;*UP6_|Qd'[#hv { ԪPp\Yh":! ow 4ݱ [43p *oK%3 4 ay/A %0z\ OypP P%/Ch _RŚ Ưx 5p0: K0:0Ab0'֐[ , MGV5"VGr,H621 N/@a_VRZ})(dɫ\Iַسwl׫ \2o1L[( hؽE1ozl @ jD&~ Xčvw;`)pѐy׻}OmR2} p+H=y<^`۽iS<,1o,8F gHoY7xOȱS=V@Ekyr܎)[@rv E.\ Q CtVOV5B FClKv>E dRDc^̗tԃfo=xMkA0+FVa^]g=^Ȟڽ^EoO2䄊n>ڡ8!=xF1L!pl,T)Ysq oѸO{O~o >0?>?pB?/qc۹9'?@cb3”p*u|C2*8;b 1*r4t\'"p);;<hz44ՠ<:M`9,s ,B1=9(Z,#PDA=k Ӷ[<$=Cj@B 㣽D\m#qc.VɃ<.6%/0$s 28[]Xȃ8 4 @n _E]1ƁF30N9}j&g @b<yŦFBd h^E\iFƂñ;PF|Gi^O|ADPӓKGo@\85 m9G(>ٲA=)2:~Ir0 ;p((pSB)4"3B¿ ˨ Cwm. Â$J["L#B|Q, C*)1zLa9EGHF4"2N'Zԏ-+,HTD[(=MUTA"D5M-TI"F|T̂XhQ8愆N܎B͗gdpU\& j0 MS1K^`d-S8d]+f,}&UlUVmO^QֵrUU2jVaEnWuVpM}d{V}V׸|ree@xv]Y&ubXpYb(_].p]c5jqQ#Bn2ꪶj8_(k*bȅj8z D<g &ekf{_ –QΕ3>uE6¯R:rP!jI"]lkPSknjYCIm&CI"`u]EF+lϖJTvӾlB36B."&3km{C@c6RP3o j QtoxZIu!6WJvfhno;4!uȱF0hj8oX)_(<˞pOfnX7pVep5omm+poofrӸVlq# o4WsR>BjnP_8>Ʉ`euFn3^ EI=#Vp# b Gs ?#,H5NV~y 7Xsv8 ҴRQ&_frXCyd(lfgPQawv,v'Cd,\uhuStWJ~_83o r_Apx! oPGٖLr]E\xg7@ F{xq X+1 &SKёuw xpx?3JאxEbuwyvj7F 2!Marzz'&g%! ,! ,! ,! ,! , Hjծ" hHHp"DQȉDCIɓ(S\rEyV#$H f˟@ JϳA_B|# >իX}Ȥxq騴bNV8ezVu7ܾzNs "ߐC/6 (jծS/D(s6ugU޺{Jќ^-{&\ &mx?}!AYImiB ޤdž"@]UDEjgu%/iZui(څ "@x|1!;j0*~L]?EtDDwHU!YPg@*Z餐Jꨇޘ馄t~OO5f4 YAE#Y9i'P%pZEy kQVeXkY<|Gl(v4ZFH4(NY{@.s8KJV¯pF0  Tp+ "J&J$M?L,ν23P$ѳV4u}^ YភDEm Z9}E Ox~xe}Shp *,n*z$r`SfMalۚfp"7D@E f&PMٳ?$|rtH!uU"XWP|bH?!sםay8?k(<v<̇LEt Xys}uLP zQ&΢'vk;m Dl`JBIk0D$CG$<ݮ0&X ~D`>P"h~CU  h`~8gVLr|&PK|=PT^#\ OU !D0ZE)4z^@@2 V;p0X?.LxDyDce2G<Ȓ-4OIj;DBOyMS 2J`y@rLwS'y<Ђ$2}! s<愐қ17U,c!7ilUtæ6MEDAw}EXsJ)Jy?#r"EB%|^{@ dD8s00'5*a"FY ^MvݛK' }BR)X ^L1Mmʫlq& aoP,ClS%&^Հ銈H8 * [M32\*Ġ5$KTh CQFIIM\'K١b?lfS7YӬhG+8.%! ,! ,! ,~v&*\ȰÇ#JHŋ3jȱǏ CIɓ(S* CG:ҨI͛8sɳϟ)ɀ5x vLW"1KX)! Ò0ѱ"j4ԪW]˶۷pʭY㟍E qTŔDfiӫ!%%޾.\w˘3k̹8=ܐjԄ|Ѡ!u5r ;Lzuױ= Nr{sHpD)YtL?vacXj8}zٷwOξdB?9Gv VP-1'uɱ~wLE(Vh=V ICl(]tuẋ,x8SВP7cnYq a/ Ѝ ݡ.&L*YC&\)cL;6dS ^Xj%MlW5UCwЅ1$"f )@bᩧY2`==XQLp 1/\@T*sC ͭuZPC! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,H࿃ *DPA"8@)XTcaƮc(a:j##lbB09̘! HÈ`<HF5d蔠#d:C($Ց58!QhDV$M0}E"FxN{! ,/ / *\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ$—-cʜI͛8sɳϟ6_"JѣH*]ʴӧB>JիXjU`T]ÊKٳh~͖۷pʝu-ݻx˷C~ La+^̸ CLa%c̹35Mӑ^ͺkǢ_˞Mضs[ 7ȓ+oh|s7Nuӯk}qË}軖OϾ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7?\ 98@DC׈ 4DC葳W NzC4NhrM z>ѐ&AG S$Bp#.=<9@ 1?y,O?^1mûx<<5H@ts<E\N! wP"{9.*̡w@ HD0'7`-Ý4qFTbMA1.T(EE$áؕ#nb⺨E5/jd#XFޡ!T➘4n~, 0x)H2$#ՉѐҫdiKTOr"_#)IGnҕd*K9G@zr!z\V)I@Rd]!ZR"|c"miKG<-O&zҊӜc1ELӚnD49LjҌ^%}Lびؼ8IE g:IvB/E:kٔf(P`2A8r /#7Pj^R 59Pj%)ҌE3yWS|%,; GBTbPwXr?,1@ӝ5':[S4N4"EjuHc@*Ш3F5>G\^1W%;׷v%N@! ,! ,! ,! ,! ,, H*\ȰÇ#J^tAǏ CIɓ(S\ɲ˕409A6/sɳϟ@ :"itZwbᴦ.N:c R>*pjիY]˶۷pBiM4qA,,e{aA#Q" Ą3޾.|X˘3ky`Y,G#kfU ph#XҧF؝ N6`զcWJexrNt֏Oi(S1_[=:)5}(P}_L}^hPL-xT v :,ECT$a)UVh8TC-X'd@p?XЈ&㐄ydhXfPV݀.s]H*Y`7$&fItix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯A/o?tӟK ?d?13pѕ0g7XиHE 2! 5ILJ+y!,8?M;E+A XؠiD9ZQH7gCP$" pC HʼnP4gX%a:pQ4Q%yxHE2#Aơ |IȁL !8G*IC5h"\X"7:܀a%y~q XADN1[rS}rȃ3% /u˒ Ә 3 rL!&&Bj`k,p%@D97 nPAA^ $4z2F@Br2A`AU?S?NBV $^ȗ o~_X FW1L (FWp" <8-D!qOL Fю0Xᛢ`F!wb?3qP!$oqqwT 2k Ah\hn0Sʜ?Po'ȑl ˍ "1 WP',]>*XDP:!0$u=_\ (8&#,r o7pr0v\-B7B^[%A~r|5`5d0v%u8 r<@B]႔`` v=rtz;hzq凧"OX]x">s+Ġa.&`fE(v1w:7| kG( T?V7Sxp Qohc`g8v ց+{!F#xzza İ{ _x';8w { H:XH-g4(${3׀FQ`vу?AidpH ? 5?d F?} nur炄w('gATrGuWRE8y7 ~GavoHP<aSA|zJ|Z%XH (2LXlz䊚W1 |]nj7Q8\eu]S\}uO @xC7iA7 d׉(n5C .WI{(GPR&Io QnI9SP9ܰ7t8)pn /7844(q{#ׄ(|7sFA2ِ=gAd|kGE?G$7$?6du` ?<@ VN}2^yi,v|SawBVWW80w`ܰy))U`0o@Ff戀gʖa&cs8@ bbɰBF a7c'h>Vٚ36T9S9WHcUEK 1HYUdEx!~q$dD7(FM!3a:ċ*#EFTQ`OW1gJ!H0QN4d$G7_%DDJl&DF據lIO͹"+eyEWJqe,(^꒗% FbF|0} KD$(m9S23w9I27Is"8yTdFpv|hf=9 }Ӕ/9PS|%AKN_f4e>9ҡ5D&E_x+C5Mҋr0)5e Ҏ,S"Q8JJxLMnId<}Tk~mOjU-ҴJmhN(MSGQљ2\RxMj[9Wz vjZΐeH:UB6Jͬ>oIԦ̩%Vejc˚QԩuP9W~u W+Rm CuYb-kMsKّR,WhQ.׫kl]Զtvt~}sa۳t]p5*>W yM 6w%_[ʘ&qYֻwn{)FoC[a,&x r9߱s\srӅMs Ѩy نRXiiZuU/8瘟HNcRgS&ưMLHnL&)c "s`2~p/VrfgOk+MhoF44nJ)~ODie4w@N{yƚO}鬺y5! ,*i80)\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲKc"͛8sɳϟ@ Z2&*]ʴӧPJJтH Vʵׯ`ÊZɪ]˶۷p4Km6x˷ߥtLÈFǐ#Kֹ8˘3kތ97opC.cΨS^ltC ci˭sͻwF M 6y-VhW&tvYy($aZ%,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.nUC8 VC?_<)ߡ;a5#7 2?@K S ÌP8ȣ>e|*Q> TyإA d{j饘fzPY2wJ_ ") 2GC !%j+*`$l9!p@qyIC#(ۀ yf !6+v~䭵㦫ѹ+Mホ,\J4' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIR+)8āIHȔAʊB 29p@m峄䟆 "An0 MM 81`zF c&q,MVF404l. 'H b0ggOģEAYPfE &AoD x HA f !pF9er Dا@r\%L~)hr f#5G]"U5xp'mS V! o Vr`}g[Wĵ yG9;Oz԰h A{mL aVvmA;+]AH+vU yjfdX` HU Ɵ1#aM_hdU$D^՚U`bBwZ 'Qm725iAMiۙTc &,ޏd%l*08r׈zȢI 6'ÏܴXgL2=Q5c._\yC<@OGn`+<bZs.Eg 9}z1!vhg_h 8y8X!pTEC8?r $d  I/ ! ,! ,! ,! ,ii80)\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ%D0tI͛8sɳϟ@IP&BH*]ʴӧPJHQSjʵׯ`b**VhӪ]˶[diݻxݻ1\ LPf +^̸ _L˘ȥi t=fM%i60ϳQ˞Mvi5 HĶ N|_h УK>U`hNi \#עë_ϾŁ5wnԽ÷}( mo4 6XYVh]4hO?Ȟ&ǍG [̗|T?2 z~AC}sGj/ 5 c0?G# ! ,! ,}A^'ݤ #JHŋ3RD4iIɓ4˗0M!Cx;G'bK! 8đDbPC4:5!Մa2Q.\tj_4ȕhP5\;z[DFfӺJ@t=H_M ߫~٨іKBCv7&oALS j[ KEkƎ a.AT: 4BwNMy@]#ˣЄV]xvr1ڣ'rD^{Q9B8҃p @@YA8~u^]Pwv ?2 @K wqgwL|D3@݉aTt6FY)G9w"Aq#MMQ8ev 䑇7pcNu{<7W}T8hHER.P(@s?!TC2_uؘ rS7̭DwRܒJz(7y/dXY5`ןOQϷ(je8&t$:~m[R+?NeսL)+PX%+P  i0HLb1EdE$HP"2LSTSk! ,! ,E H72H` B Qa… ȱǏ CIɓ(S\ɲ˗0cʜI͛8s46:bh3НPJJիXjʵׂDl(0a]˶۷pʝK& #ZpƁKÈ+^̘*XX¿><ϠCM4 (pكM˞M۸ %P=wȓ+_sn XAϣg޼Ë) 4Pwkϧ_?{H_(b7` 6T(P=hfv ($h(qȴ0#34h8@;.cH&L6+F)h.[ >>L2\h$T馊X9ct A#&\O栄(-֩hx6c 褔Y襘^x18" Y*)[Rꪟf*!b1W꫍+lbF:+6; ` Z&:+IIԂ(.nȵc?n*˄Qȑ'+IH#nf&lrƴq*ʻH?^L[&/s&+&,,Ld|q2a$И$6!ʍ߸s?K |1I#}SՍ\5ZoFǑ yInr)F~ !/ÊJmvk'1Ÿ1HѢt.nbcЛ[ 3$4=,>F#_4U5c1Iڧm$+!?b8oÍ)I sw2Q˅e0ɡ&S\c4o43S3OvzEJw9pe0RƷ@ H$ /-Iy nY W, }!t_otqyQ?vHxL@%XFL#v0#!bnt$By&?xUS)ȀBG49W<臁Ӵ$H&ڈL \ƹ!-.lj2ŔޑJVBENHB-e%f8m?Rh0j$@f>x46`$N^Ra"=8 o<DW3Y->>oOfOtd|pLWΜfd!I|%YALH`6$K& ąFGb.22jACPrlcdTO!~ƏHEv9]fi#BIdAAaz$ݔgEMb*DG8 XCҰ0pf5;yVS+HN3F''$2HfЈi#$l ߌ$ 4ЛeQ- y؆6#sM-A䔊0"PDը H챾Lx[d7f~e2C%U`6&\6w a1=L(&1'H '!T.+X#NL" 9H&8 2nč(=qd'{DN2d )>V3c&d<4PAl>Cd=;YCN1 10i2Bo8 r/M0?ԘtѢD\>4 ո61wM`,2:]?]bm'f)b8| BȔp *Nwm[miB`cE>QlK" ^VQ$DJ7ֹah;"160 &+ NgH!q7 fCVt *ƹP<(۳tw Gckf6woX6sCq/mGA;9Czǽ-bJCDB8XM5ps[dd@&7Iv&z2׀FJF ذ Wp~ BȐ3wbcS|egiH@)8u7d E IPEFcbX}aaWUtki0x bd {%6yP:@x:Ee6jT5hdY00Gedzv7 FuXbnA]&EE& ҆ i$xk8bkgyeHddUZ8N] Eub i Da@ ukg j #7ufr#Wb U{bimi)' % z hsvscr& XbX!d pgkfFH2fweeV@f CF  lj ipeVǘ`VB&bXbtf#lM֑,2ig-YQjf36hFb<ٓ) JY&PybK9V Tq\)Zѕb`Yiwc4in)jjlYfxl&ky 0 vijyY_!mV@I 6 MmmඐFnٙ\n tbydf6ogoFdop晶wp b#7q׋i 3G 0bՐ d@ % p b nj$gr(r,r0a5ws9sRyI@CFtFtgtLtud ud ܰmi a p  H& a P'fuXu\z_v?Xvgv6mvdGwvwٟD~x7dfAxv|Gbp$Vf a %a$a t&fyyyn(zjVzozAiG0EZ8{B|7bxg|')a \jap 'A֦(}W}bp}}}JHF~f~G4~Wtj8bkZb}J#zd'(2x`a (HZay)!8Xb'(c3^(/h2JbaԪ%f y<>iAȀZFք#<,Th~aZ8ЅW:9b4+cAaYgް nZbg(e!WpȱszuxyƇ懄ٲdkXBy8;v(0aCEGʡd0 ʆ'b] 8(aFJ:ځe!wAO3kg Ipp vk"  b͉(ai͈ 8 @f8^ܸ ሙ&;)؎'pVJsv>f|Ā&MNC6l i)c3rFpY{d#c9jXi(,/ g1ibGY(0ܕ2BIbDybܔ/eKHܖF,ILRnP,hIi1;uYVmrIZ\{lMsl7yo6ouX}(֛ qi&֜Iv鹞"מ''rvaPv)~wAEujʊ3˷V{'+ZWs0`bGvfvvlGnw8wuwwji{GxhkZra@BѺaKjpy˰y}o8zxJz8NJ{ZΆ?a|GaƼa a ۭƪZ窰}}Kbo- C&˪<~fa|i:劀 xnȮ(9MFiPƂ0xl ͩ&h7; ,mi#k%' Na]/ W(d3˅v`xl[X ;VM{}iHmzW+Ćzv8]Xyd]gjL綈a zZ|@ wv ؉K}%gù:IY:̜Vǫw"ǽ ћ{d۽ x嫎츛&>ƾ(g0VKLƿ Y9 lk9Da c+❩NYd&i$ &Ln㞹DNyFn4 b6LKjM~6"SNU5`b>d^f~hjlnpr>t^v~xz|~>^~芾>^~阞难>^~ꨞꪾ>^~븞뺾>^~Ȟʾ>^~؞ھ>^~>^~?_ ?_sq 0 _40$x 0!&o(+-//$A@p2O<_@D_4@R/aU3_"(oF_P/5` a @8?**iTd?zAp !_vG`U@  dPĠ 5pbOc|?/ ?2"4[O@ !/e?1#!C0KSo"!ql/Ր ހ =`O%DV / DPB >QD-^ĘQF=~RH%MDyp)M p !p s!A4悕\5T^zݿw\i㸱keZ˺Ǜ<_zd4ʌ0j䚄+n-6C?1D*CԬGv!,f*EY IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ *C!qcrhЇg$ ЄRCgXn$Q & nF(j؜%⡏~\0HV Tի!8qL8H[V D9 pO }-` 2v\ā!UJH̪aPT+ԁm1ׁxÀjA {j[;j!jiUڂS؏V6Q!W೰[mKcЄ7 2Ԁ[ oݛ۽wh i*^AȚq pDȅSބL vMkDY!ŠƏү{Wz qǎ;nHjc C7P8 aO{ d yGI^rwȹhb@m1ƛ-@&n!hXNFv ozt4/]ZC>nj.U} ɷ_Z[ZV8)xĹKk\y_PhG zt\ %cxi@OBۊ0TII}!(,9&a/X{Ixeμ}!5r! ,DH 8*THÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗+ʜy͛8sɳϟ@ J'ͣ *]ʴӧPJJ@X Zʵׯ`Ê5+VhӪM(UF줱dHQȉ0kġTðMHKt1Jʖ%Ĭoy2GEy&z㩷c;I6KGmF%o'5*iӨ>5pɴM#jԪf ݿ+}{?0_Sw^qt6iAXsy*r` zt:0GdC" !P5"8TbCa $Q7SILT3?8hR4xRKcC*ny$P.S)g_CAyS4b'S T9Mf$GP̱I'Vh(2^xhF5Xqy?4dlT+FzѤD8jr.y@CM.<ă-\ v?j@1P35} DCzꐫ@b+Z;d;l&f[͚@5Lb*5 @Ѻ^[*K* -+n+?{p;1:Ԡɠ7 DD2ⴃ1?r3C; D :,/#"N;06:(Μ9"y8u 1ހMV"7 .܀#/6% ?=KO?̠CB&z P*dH'xCh@s? ( X^@@?7@HHQxz׎9/P,@ЇMB& 5I r$?@C\!5Nhb4ЄhP `; Xu!VBu f-lRA|J Cn [%>EȠUhDEaQ(H7r 5 ^z0Tf8Dk8 0# !"ևcYčUܘ Q!eU \0^-iZkVM~͢UrWF1]}mS]+ I,j K![*We-H*frh`C[X d $pt ?q tH I d@8E;Їv!"H[?A<5oeDU Xb9jFKՠq]b;)Ma dFm!Z9'#}p?а 'RB3(B,$ ;B"U̡1H 6u)Gw0U@! @ql8+G_c5go<1 qT|)T# $.F6۟j Q)8A@_&Ԡ&|΃w4,l+x,N5"yH=I;tكjR}=h@!4]ImZ  }MYC k^ڌF@=TdH QHorY iAra$!`"rA!wT:z=$hsuF@tst /3%<(Vs0SfpLwTeLfpVDtBw  ng4+ Q 4^lFWzqVn1xy rHWG6KyTeX|0k.p(uh|~Un8@ܰX83IFH#\P 60E+G&E]\ROMa M|SyC`HKŀq%E5 <69&*t#64HD7cf ah,u|\' huX-؏Cu=`6y (F@fKT!U#Q] ZHwtd aXђ wt(lv(&y!VC!yg8@)BIzXVBJP=i6?I•`aW{qA8MB QV2LHS6M0~З~G ܧ }7VP4+Tj&% TH_q_T#g M fcfq `X: 09N!s!&";BP( dNY`!ة!a֐iRGsit)PX\7^'eio zIYf5Pфf[SjTgcL7 gG-O=` r 87aN ڢ#j|,ʡqĀ#M7j92J:p2JcWf8r+ʤ.0 "g8 a 40 ހp Vn rUm,2Ɋ91m|W '~pʇ5v[(1PTt0h(̨&c"&A00 0 \` "^(NdNEA:O QQ99 6s8ݘYìb!X jXP~ԐL T<8 FԐ"qwQKp npQq?"䰋]qb8pz%axV! ɀ~"v +azsȒe7<1ɚɜɞTə ʤ\ʦ|ʨ!5ʮʰ˲<˴\˶|˸˺˼˾<\|ȜʼD10 K \R'T8@0a= lax|͌ PO) AP}0A0 m4 ܰ)m\+m-@ M5}?!LA0FL|LM q&m@=ԡ.^`Rb ql:/Cu]geVBPmLZ1؀=3m}d͟A#="=Uټx#- py0͆ц=<<Ψ l&0ۑ-l]2qj=9׫t=܆]pM׽د lڌ̈́׉}i]խݾ==MrMmԂ}lݙ]=]ؿ-}/ g|nLC0&a$Ӑd1 =A}\ 85 9-T dUm!㻝5ʽ}OmOޥ.ܝzX>弝^5}~ڻb.NmNaK\W~4NӇ=Al >a">>1.PZQ1p^϶Ͷ=n\.}C 4~xgNN]1Տ~>^k>ҭv>˾o^hO.^d>qSVy.׈ǎ觎N.M6U40~AچZѥN"ԍU-lf.Q:>>.N>q.}~PѮɾW?t~Z_:ھL@?=a4XޖYnͩ>;N=?@aё=<ԃO4LP5( 5sD?qb K_e [.~~>od^ʟ+;Rg[a>_UEĎ%bZlQSYRY1W2%T?>oM]g;w_L.X*HRG8pHYd"S{7*M`1ָrkĹr#Im_u*yȧ['7ٿ]jG!'GVpS+^qT?0XϸLϮ*-PoAoS¥.>6̈́8^zTE xFOZg3cL3.zx$S-p(Z!(񿧜#2J? 9-{/ S;+lk(1, =1ē>L@QD۴2A8\S+HMJ-B*j>F"mFlOƁ"^u#iUuYf "k&i|kY%W#M5B19r@l5?:\pŬHewƲdQs\|SmTAm;Ք`:c6NOo6_qF^f8@\h3*"¸HMD{YgfE9԰_~u.gbN~ZNG5zOqvZ߃j~Vk^_!hF޸JWP55:¹91m~ #yN;y y駧>{{ݥ>l'{g}߇&𫧿o>`7y^@6Ё$`g@ю ۘ <`?H vp'dUH=%uCF\X53D!P2!jx >+Dp_Lo8cgUtfo0i\bhDhiP$cG=Mh9h  (G+#<bc8gC$# pCX# ɹ!)h G#aBV1eXP rt$pI>r!ix*K].$Pe.qi4c,Cr.iF( VFn.~r= p.7 E2p!3`8pS(2AA璇dQ  g855N͌nT~ eOrs9(AYkz.`^S_6fT 7 OXX}HM{@[ "u1#-e5 , i)ISp! 7cЄ7Hr׼d WA5E# p7бԩA,I+7h9qj*.K.p"G?qmAq[T$\#䧸yΙqs$4ztc\_R+d6˅EIM;F<3^W+фʩ1Hw5?n{&?$ Џ/'zhn]-2wkkȀz2$J?"Ƴo |C !QJfB}GY-&( Q$>LP_B_/:V^H?R Bu<Bȿ]!υ<433I`?$o8&p>;rPT+ b@ɫ?r &X/KSƂ>?ؿ +o(LK74: !k؁`j¦2%0*pSӹC'3? rX+.{.[{3}v$q k @(pA$Sdb9; >LY2MB/|t+J\?$D Xp 97O Q4] MZ"PD+ÈD8<`hCzIJ0,W$2 KE@B^h7=7.cC A->G,'v$@ECf HE"OAE, y4gtF4t[MtZ4 p`/4$HZT k,/l-=D\K4ÎďTFaɫk!pCxmlT)"r;LHHr1,3LC~dC |2K$CňXh |NB;ɌmJJ({GQ7z;pX)|MKojXhL?BK>EX#ZL#L ̴MI{M *JSz=0w+h!9BNxT2ByS3ɶTe1nxb6\CU?1#dQZd0Eu 7t8M3B 3uNȅt"7ЮLu 3CB+p**uX, =U3MDPv t( в,OQxj8HeL7P;Q#% tz8:C:lZȣrȄIul,*5c8n G,1z+C6/vTS Ԉ8C2О/G{˃uPQUg`?WU 7h$QS]^Gp$n0.` CULLb `H?Vi%Oa䆍[3oXWչ`h8p2M[[VEUz8oub=]%^=We׼PVں4 i* s} ! !Xe@XXؒ! jT! >I[K}~OaْٙY" M"| ڣ".naxPXQ=O h$<_ϾC$D2#gB2ĈC8طu( iB(UBi|υLF40DF>Uxa>r24qG+`É( 1^3_Ɗa+ƍQB{L6P~DPg,4,d 7N !Зa&<1,$7TPA?Pq, ,,4uޙg a 6 Q aR aG#gaBz)zx(C@"h"_Q*무6C ] D2dL.ebP :Fi'CT4P?چ'MF!{%5+X@!Axur0Ft'VCIr.*xdD0$ ފ'>Is] L~2OwHPV%DeЩ8 ,hE2юz"(PT"%HWҏt'¾Ҟ8 (cvӞ PTfHMR 2P=jSJXXͪVծR9 XӲuDMZԯɈTVvxV:־g OJXsbX*_JvNeLa7{ö R#KNֲMm0{R8Bg%8:ֶ۠nu[> p$`d 1m\òb}Kݻwͮs[ LlZkYJUzz]{c o>[&>* aUV籬]S㵽 #X|#~=_Fo2 e(Bx0[2D#OǞlď2 8H6N[yx㍬zjWw;+U$&nt ((!@V^qZ4QIQMjGMꮜ5&k_aMO?AenKtю$ Q`0wyD 6SΚ 2^ um1&#g$Ċ> 8ܡ[:ӇVyfUB@`ն t\={Am'`dc ݡmfHrL>w/n9c$+ĉRDHBpIa pDIq4* "9c `c9nřİ#g__Y͵vp P!UȷƵԥ8j]Or 0\]/p٭u퐏@Kcзī?aY߽KYH#4aiƁ}/.O|T8 ,Ј$=/ Km꽰J=b1S.(K\z 0'/_2 ml**Uվuq qWW땀GU׀>Yfy%zVuVq xhYwU@raꦁV (28a$(w&^5"s-(Z/^18B^5HtU[g؃_CUw_*H[궄L8ZNH]P(`(aw2qOON3 pQ3`3W(azwrOq6ٴq'"-tO<e'x;d -ԴI6IXOܒWDQ(D7h8؋n1!XHǸd،aDmC"ڸCa')-i0긎 P! !SĎx8ȏ(ͧM؏O61Ȑ99g;Ø)$(YEwR)(0Y6y8:<ٓ>@B9DYFyHJLٔNPR9TYVyXZ\ٕ^`b9dYfyhjlٖnpr9tYvyxz|ٗ~9Yy٘9Yyٙ9Yyٚ9Yyٛ9Yyșʹٜ9Yyؙڹٝ9Yy虞깞ٞ9Yyٟ:Z7 ʠ zZFvI$$z3q%:(*J05!*Ѣ) &:7*!eNSH MZ0OQSHZ 9*/J\z<a eze*@Wڦ9z2i*opJu{*lyZBzxZyJ-(ڧO1z*HaqCZorJE Pq^vʣ%*J0$D:g*ڢ ʪ, *:ǺZ jکު8JJ!3JڮڬJʦm :皭JZ 박 ;Jkjzڠ:۰: ٚ/k0z;ڬ6;8"<>+'[[<+۳Һ>+A{KˮnV [`{M˲mj{k۴DKeJ*Q˧|k z+.;K_Jy9;[ { `M#˹4[ӊeKLz{Z;VKJ*Ju= 9;o {ꪀ [^[۷* z/[[yK+i;ꋯ1K۩];뫼)f+ݻ{zj[~ ˽x˷m{p;\:Ļj [ \[:[+(Ë[z'ÆD,H|Û˾ Y-[FL];| T|M% {ŝ-Uǰ+Q)QQknܸNuȆ<\ܿLsv˵3^~ގ8 oP @ 3a ~ @̀ <P v 0 5 ^n +N1> S1ސ69 @

 J\#,n<@ `Հ5|tnx~=@>'&Dy  d^bnyP0t @!$Kz #pqqz :C~nd@  L^0 ^a`˥LP C@8 3Q_C>^`Pp^4qP08`QIn(s ^ 0` 0 n~5`@ Rp /8_:A)p 3n y Ԑ p P4W_0 iPyp d eKS3jX3Y/`?c_gO i VP|L? L Omt Y.xZfj r}???aA ` Ԑ0ɀ @ ư@?? A ?L<@*o>BpցG_Ƽ6ir;4.n1QIF֨Wǿ4~L:0jZd=mSHN6鱱H!PV PQ u)ZKH/N!RԺ4"ҳ " wjZD"caݻyXg"P9Gg;lڵm;wUx/`R LI)M>m/DM7 5X}=OBh}Ҡ1jp$&X<ߣ[8(jr{J_S, Ƀ+f"7n;#<ԛ=7p +k!P@G*o /@` D E;/= ۳G[>L,PEGZ"B S\J"9<$CdFi&$hکt9c!j!\ؔN;䡝D2ᦝkd'&>pii'~i~h\$ZpTgF~dA &#=)Y& ʺe486rk|+㞛foF7t|vz d@O+)r(yg~! L NiǍ>CH'<ɩrJ-<*TNarr&TaeNr<#Z1ίD" auY3D@-!?а,W'DB,kcs,,$ [9Bb"U!$H p(Xv0U@! W:}"CDDlM|""T'DM<.GSN.&\6s" [@'&@A$%-9dIH]h c*rvDH.$+=!ұCh)'jXʕHihp&%RVb{ep[沔O2{h'C]ڭ3BzM{ 3csn0V&=A<j7EI!: vd6_.!qbIp@?o$" ? D?0#x845/!`2xb MFPe"E aTCi3+BQX Pa օ \AWt-0U'rKWITYZWֶڍ\NGb;&yG:bi@ KH&&J#ty7࢑١17n'AV/ m:Y4N95&]m5Z;֏-gq9Hڤ~|lv{\ղenl.Q&L 7}z;MZHg=ov`Qvcq f}fϟh*>I=kN T]b\.X80?=#"MOF`5q+ ZP"T)I Ux ?SU/Jit0B3c.\0yH+b&3bΜΨg]E<'vY)Zɋ IrE$HkZenVQ";.nFAJ|GP:4 5Dս/6d\&􉰡dc'I5jIe-]G'5]OeB4P\]PP06ܓE%GBO=hrSXIsROǾqEnOɜ;*BخSn4л'3M܅/gDAg("/&g/ |jluW%yʧ4ƼJow Y4-cc%˟HWUqp;fEpnQMDOr8yN i`ÛINccig򉭻 rȜӵ /PUp Zi !(ۘx",ЁtAZ3::/K`TlA"q8LDJ㥠۵C b#R$N!`G CÐX[0ƒ຦ђ5 È9:9CsGCG BlbhڃC48DH|D<= J=û6;ExNYDVMDX?r*E#k/[pX(0pxtȅS FivHyzh0|k}Ӿ?Ei٪vX /,#Dq jr[: Cwm.)?$Kk!芯 B8B`G0 wANX m@p,`"[8AMUIʫ ʍID:HD$MxhQ,ox֑{5L2xI7KJ̅P8<Qg@D'!Dì\ȬɜlFc4#IMc DLŬٜLujN걅dMsM MTLKdc@x7FRahXvb< "7Au> >1O8Z;tuQ?uGn~h!H1ȃ i5 Hˢ~ʪ(/h N@ P؄9(MH9! ܵ,JH E 0 B mM@ҽ藦PʰA첓ҧ"]&#M'J'\J ~Pl,+#tG7-Gp2r_/F{S=uk fHUF8%TG*CJ KMN MP QhZ2 K (Q5d`ՠ)29hUi!ZmUamb5 LdMք &$Tg  GҴM=Amo pE2R Q LafH[ מxҤTZI[;u"AJ]׆ud[ }XG=XXZuTPYؓEY 2Wt@d1G,r/[ٜTЃ%Zuՙ a]h-ZiTMڦuڧڨکڪګڬڭڮگ۰[eڱ5۳E۴U۵e۶5Fۏuۺۻۼ۽Mڹۿ\%5[UŵVuDžȕɥVƵܿ]iVӅEUu-ۢ[g׽uT݅]5^U]Н-]-[%^peFU^ԝ^Iu5k[ؽ]ѵi۷ _U]罍uZ}I^uߥ8Te߭_ 5K:%ߎm\4]~Z` `_ii`_`-`˥ju`m_`M$^`^b44!^b~TVa"n^N]"Nb0`}b 2b<Na]Sa/>b;}^ [a!c Cc#V;NdBnd~BI-5b8dGf&Td>nc7~c=` b&1&H&_^el%[.fdc?:^d6G^eX>G^d6:FC5$ni^+f;6u]KeʝeZ2b=V*f^f)Jdg:vvffVN炎cHzf5nobQ~hRNih~d9.Vsd}gfc{N6虾g*6c}QFveiពfdjg#beL&&jhn&)fچn鋶iu瓎ܔ`^da鲖浦d{jvj,~aiqifFj) 6ipnkD&hh6aknfgNkNmVf`~flkn.l@d^l†jfkŮVg֦ml~\-Ll<]F.ggA=gV/W^mhgmFMm(vJSg^v^GNddSvv&59~c5. .ib_a4 7ٮ`+pmc^Fޝo7_ "Fp wo~.%ojgaqI _^='^l'\#Or)P+-r/0o,3G42r5w7s! ,! ,! ,! ,EIH*\Ȱ 2 !6Hŋ3jȱǏ CIɓ(SDNIbVʜI]Ȑ%ɳO1rӧѣ)=@7 D( EJZ#1,T1 aCTօV.Jb21DJo47 H&ߙ0i\aeي0VqU +me*axag65oT7ʙY-ܻ{ml0'aX&R34a $FgtfSz- 1hF1J { |V10T?[~6Y4A$|~T4@EF3&Wa`EQ%Xq.yp4Dj%1o,`HW<' ˠVL&S $2BQr,Q4}MH!An5f>d2@e`' Xw%F3Yx@2`sa2.QnEsܨAxg$ATߞLyPD`AHdƢ]# $\%'C!*!'0PCq.e].3h3ĊrHQX]b`Tz-iG Eon(<+pv.T&ABT Qu׌b[! !nDFyO 252bx;P>*n_~v1ALPaMʊ$5Vz,5k tמ% ^zHjnqF>*ƬKYEA8q4 ^=pAMrnzHsSw駷Nκ봋j(*4{=/<o}=L24G/W=Wgw /#o3oO/o?o<0֛K`_<:#'h@7u0@A훠 XA^dBo 4! w8~-?bclІ!Ly>Qƈ4WB&j `l\D1 +"@VP~qWEeYW.0n31b01B{;GAyD# wFT |h%c`?G0)!=X#Fb1N`z d6=JAf%n;+R"Mw,!FF1S<|Cy F2Q4O 3iFf")K[#Z>Sz(4S8+XWBpiGjf H"cc8A`\" ;K0BY mwF=IX$CMu#4Ѽ@,y/ -0"z}^0X B Y!D=" *?jRv%\} 6^FeEyS6yUa{e,> CYѳɐ7•t"^fH{Ò+1Q!f2&<|pK4i<_*UK{8łs;9*<|#>[V/" ^p0'w}x|=ps\k*g˧\7|x1X8siEHXXnu nx-gTfMکWpNxϻN:ѺNgkiOk}{fщT;7{~y[h-M[>ֻ[v!hUw]C||Ou?~ЏOOϾ龰ݤz䞶O_;W~`uOD?co _mY7w ryX7.7 wuwugPTzbuAT 6quHp$x19r4WX.xhu!huRW& <8ww|~VуJ8w\YGz"KX'{9g\xwC|]8pw|g{~axdȅI~w|ju`؆v4p! ,! ,! ,E HA*\ȰaCf#JHb2.jxe CIɓ'=2Dz˗0cʜI͛8sɳϟ@ JѣH*]TP:JU᳌*LƵ#FQK֤ʦhӪ]˶۷pʝKiԻeW X*^̸ǐ#Ke{3'u}ӨS^ͺ5SJ9u긆oߣ.ȓ+_~oCb`:U 9wb3O'=ꚩ ^T8[B ow߄QHHb+EH#nt8h(^^T 2B .H8F".sB+BH&L#dcB4҇(Bh[ޘ#,F }GHtInr) } =.Ê$fHQ&%uLnh>̢? Q8]Re> GcRXL!h)m"iAH<8a)$I {n4&첫E%a`WL3 YJe jla,!+2+N 9R`x2I,p[QBFLnJBHd[1]2LBR"q"{Z#!!V|4l9U (Le#U|\~BElg?a4# ˠaˈQrzڋ!y!WlW"IrA1.$0 %'L%Lf҂U%;uHd4xۜw9Qˤ uUu9*VX3Y#)/CI| %>^HTDoT#M1pJ9K._)1SMZY3MO#\*\; rؕD5O塕 Dy;iƏTت7Nf+6gyWy C~N +%6`SHV+hNǺf"%̕iƥ:hL gGRsQA]TYC{XYdxîh(_5k"hP4'D3ĉid+ b@фiAn0hbB+gAa2Dc n5 Ѐ+mv| C54pC.4WBI7 i9 Lk:kh™n1rA]x:l∰+0=4+Osk;h~bVe6JS}0t`nyB:No"MJCդBf  ))@2lڙٶwve,n:F7W`xY jVab]Ӳj2Yg.P4,4.-)ٹ0dn-rsaXG_C4CHXI!=7+VꈒB7z<64d,"<>->MF)73Ѭ\4}0ܙf7gxڙYM7C+S, &6j n̟tc֌VdDݠ}ZZʸI~1 zbbnWiLI$4V3Z1 bpZNRn@DJ@cdZ}4}f TL`g6e:[D]@ D`WJPryeWP \p Mk^ːtmV% z S8`t{2_Gw P`X5M 5W5PW\tgb7v@WWYD LOOVdXD  WKRip'N'KyzpzP8jMJNb\sXPgՇhPXQSՂ_eT8P$S-7g(̸PUצҨSXSM߄heB884T긎!x؈VNUx!xu.R' 90ِB! HyIU2Ay&Y&D3Mu0Y h3/1Y3A.AK<9 h8 \,Dٔ YS,ALP-AyBN}FHgZ9Hc%.HI @pɌgW@-тqٗr 5U \7闈M7]J9-YhTLjM@Y uצ!yWٚ9Yyٛ9Yyșʹٜ9Yyؙڹٝ9Yy虞깞ٞ9Yyٟ:Zz ڠ:Zzڡ ":$Z&z(*,ڢ.02:4Z6z8:<ڣ>@B:DZFzHJLڤNPR:TZVzXZ\ڥ^`b:dZfzhjlڦnpr:tZvzy 0 Y4y 8ꙆJ9Jd ]- L`zU-Q  JWP/A0>e*5`` = :BJD٤  JR*: Ԡ @5@ PꧮЪj, } P4k\@ ʯ..qڰj-!C@ $5)43k2ʕgcB9C,V"V+UdCb`:vҬ߅l .*3kܹG5Oo0} s-V*k 0ṆjKlK2܌RVnj&7Yyӯ~'VNq9k+-{!4EdӞȌ-L%L6| 3d~BXN?DO'HQ#6›ЩdLlyl[QGh%ylE3U +^|.B_eF0>1#J$S"$$J F. W$I$3H1L22?QLc`>X^ЋShxy?#]!v&I4 J\&O1,d>#B&D}d@L1P#4.=1ZVd: ~\Ls9 $XGypB`Iч/Cy,i2TBF’GCQA*s|DjAjCp2iF}zbntFȧ_xtFGԩB=B"ؐbkB~`EZ[Q#Pz.DӫF3Lɺv7ⰴQnt%ӪO$QKh&1SNBZ|_ S$̗$Qn4.]/Gܐ1؆«f8sDHX-5>\~kZi ̦LRE\0!zb7LP+&9ot mW%dA6 1h#(-y/4C4"+ϗ]X fGj29'"{bʼQ3q/:<z> -yw{=Z2){ŕNE:F͠G 4 CzmDh%4 :Q/t Hz0"dRŠa6Cd}'"òd-# 0(H=Oė@[MeS\YbJ8<"Sψ4ll#e.ҖɕьtL՘Ga:>$ $%I0T̤&7Nf NpFP NH*`J*| _)QyJLz̥.w^rN* AΧfIgVw/)`0,iph:4(sw8͊ɔ6F̱tǑyLRa&%oKz昬Ox=O;r/O[%7{ϛmI=A/y~Ͻo{? nH@8W 8o:='BQ6Aۖ"B51 _1hK+'J?oҘ4MgNoӐ4 RsnԨNLՠnO Z?zָVw-\Zp~Ml +f_oÄ́g"z6զ!x#}ϥش;7wQb&19.r;FA Px">(D'6 =`~`x%MP_1dqv\9Pz A%PNΏ9  `B`v6!(h$*3bH*^Q;ŵ/grUJH'vW\wn*ЕG~wIVD%)M TRů#;ϽN!; r*B%*MRtOwmF}o}aa?MT˫ϳӧ@Cd[J\K*ڄo\w҈}u\lڦ k;᎜ɕB\EBp~DvЉP}("cUV~4}4ҴaG[5X5[E99zP}> qG7q4Z}4h^6K|]v~B[[8b9~P}K@: !z+.V9}{х^>H^RǁK1V3rJgPЄ4 >vG9w_mvo7wN6,EЎ~@S+ '+.{Ï#j# @4;<DByVJiHٔPYN9V9T;Ֆs^Yy;v|f9nn6srYf89v>E8Q35C@za08Ր$hYp8O)k Q! ,! ,a H l0ȰÇ#JHŋ3jȱǏ CIɓ(S\RB0cʜI͛8sɳ'O $`c`(ѤKP"odj B QV^}]˶۷pʝ[Sld!P- ؔ;@C~ )& f\v ]2pUb 0(f&by6C|UF^*蠄`@NӁ(e JdA2\pE6 駡ꪬjMdꬴj g( 9&&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣w qADh@B^!'C+HH#.HĐ @,B2Bjr ,H5V$!LҕeALIJ&1eC&Y+\F MР0YKT%- 2́`6wKҗ9I΁LAAC5,I^b&)O PyDe$y\gAr AR3$E:LB:3 H+JM\DiZ\pCEtKҧ@*QANƅ]gLSCPNJ\"@ F.=԰ZNͽËA@І0|FVw g塬TpP~g&`_2Dz r MҐ ,/B H"0(4R1Ȁ wP|A!DXChP6F)TV7C\B}LP_6wB¥"BQS` tixޤ?F 4`KcABd @WhfŐ&h`AbUy*ꨤSP*hTB\CVꤤ K5b8v66CVkf+ѝb+tFE+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,HZ2!\1u h .mIG5E[B:&Y: l&sS@הΈBTH9ΪpS>DH;2΁ͳ!L0RL&Z>3RaS!zX?@?"KBiAq=tbЃf2sB,BF@fƩ 9J p\}BdZӚT״ + 9ST氉N4CQU(bG*Ώ4L-k`k phGPe[ݺS2{}AQ,^HQ)†t!ZŦ\*B\tzAPb- U~h%`㳟h-E4lաz?Ⴣ`I⢠ o{\* "J(6zZ -o} \׸}.s؜?QjףPEkd1 Wy#! C !M]H5ߘ\pB,^3&pbbui-'KlbCu@NhhAꦸ?06 . & *:Q X1;9$T3/q*>nd%3ɻ\+g]r|;f,*Y @ B."8!i[~4h\t wrK#B?ֲ'i^PvӳӡY@kp݃as-9Ӏɹ4 Zʷ;*,t?-8v4.߻g1g{YvR*\yN{67pQ"p*3ӌ].Q? (@e; GK"ջ*;^;tQFmҠ~BWݝ7rUԠEBYZ{ܫ5oE:qRSW'y:!^!p(+s!ȅArwvxjzp+zNN!AAå!| U>A~Ҟ-^{Bw+CrH8T&?BoqL;> c'ҚZ9 Z%sr1kQvVw|$usdWbFkUKU e{jUcT] uXK(HHLhGvf~Vx y V}wK< 1~I~ q&Kq[V$fKjX: \ZTsѵQv7kؔP%EPQz-o>ЁTQ]:@^# U)Guh|w(zȇX F(fvwGxBf$ q _,ep5 WR 1ׄLtMO y E&YM&rTF]Zʧ[obeYU=v4 uЍZGPdĔz s\ _٥*Q*Zdt_ڦ8 QKȎHk 94(ALj pVd y Z< kRKhL,i/9'QtM5X LƘ yw`9GUYNE   s4mD[fƗM58\k[%_Qi}و9hh U mtp)VPuwM 58fk!VLV W9xvd S wDə MNiٌQUMU9<%Qh/!yGWE.} A QFQ p LA 5E UHr9֝Q 5E@ DazД35R ! ,! ,! ,mE_ H*\xF doD CIɓ(S\ɲ˗0cʜI͛8sɳ'nаh깴s[!~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7z hGKҚMjW嵰lg}mmYv%p\ W c3GyY1F580J[(AqtJ81 績]^HwAE@L‚]4hw$`N Ʌl` 59D_SID`2y5|V=ɉS.+FNȑ\CxF$g zct/A:A(<4|%^V PX _nNtY:KUBpd 7В6ؙ x3aÄ 84ݰ,B3yXhFEC2 `HL yG=GCb S6H dY%P&:0 P5$55<o\n"Miy"6R p3nEA\,<5 715`n{H.pS# `7q I۝xϻ+c?x©_ ׷%nwq즆($ N-u*.#\ w?kS|i;Ǹσ>u D7K| h-jn@:'"$C@1vlȯ]:zpczN?~a o,;9P]fH֔OQSlV X(ȕ` φ8y@ JInA)fnp F8p4CZg! [yhthi?C@]GS 6t:`珡i>xp35?WDeqC 8DIe])ً W$i 79]wyY!w`Y@ea80 8IV}0J0$ɟ(8v@p }G$=86Ctp?ve EyjCDC 𘏃S^\pDPb0xI%njyjߘ |!g>/ g8h 'P 1Il 6fy~ fY Y ~GЀ!0ρF fʥijnɕ;0Y ]Z~4Zʥ^ 7elhJ* q:u ~9n!Jv<PJ ' n[pgq'*<Ūy IBpD^ a0 7 #iK!%9^0Q)uLZn}QJ 4w"fW7K+'X}~Pp~.Yg+n XVPpY]K_dkhk˶} tKz:`>p6@@ 4 nE3Ne SHۺ DiF>n@ A 맺뺔q@yq; nQ jλtk;]Kt[{[J{˼v)[_qw?w k  ,֊xt>v:<8A II1.Z1505` "!y%qxe>@Ӥ1eA&է!f)v6Y~'zĠЁRL׶\F0h^S̀V|]Ņ붫^̗+ >t yA{L  n@40pU đ̇𫼷;{ 4`8| Zskɒ|s\ȞL ˏKʚ<˲\4AѰ@̘BT P88όzGT axŀ Wy)d@e(;5!iвq+LaАW4wie F<}a^>Y[XbLs;+Tj c"MZ|ŋ C )]!=%}Y[q̸}9y p)Kwȉ L1d JQRsD|C$)u\HՋbm6أ]IcǙͳװSy=DJדk=zֈmي- ʎ]bcxɣ h p͚ 찔ixWE!i|VFj ':)Z2{5`dgs 9Еڑv;'5.)iVSJV*[&BPY Q}Y4 fnૐfP0*=fb^uoӋ;kE GM5ј1DŽ&Erg1=yDƈGƈp 0 e}ɗF>qx尀J@MO?mc0D o(敌|QeNZKp=UNGuIN^[8  p8 vS闞Ew;lwgxк}3Vcčj@Ξt%0Y D} j a  ?=:?Ze*1I`x e+%>@y[IY)/ɓ6 _x؇C_rҫ`= O,7],݁&>%јO$ 8p$G{Ԛ567,[d ӕ n40]HJ@6]I`}1EaPGV 7:Ph0m[O`/@d/~goioP{_ ?ްz8аW gvАxj'۴.zq,>_{pވz xL{E[dG! WP6| &@mg}/5EQ ї 1Ϸ~/b5QiժJz6)CUpaÇ}bU52e՗#gjdH B BhbB4rR$I4Dpځ48t(S'T#'ܯEBiԈ_Î-\u޽U^} FX‚?>̤)ڥ [}y YGXqt,E(y:)QHe#X(׸[5pᄍG:&nrOM~vw.rKQ~6 YyQ>VbB)[x`LIO"@)dEFrn? '\p!g ;$Aߤ1 )Є+W,E)XA' _XzGAH#dn.D'B2J)J+2K-'2L1$L3D3M3 QM7߄3N9礳N;3O=O?4PA%PCE4QEeQG4RI'R>e)yGK?us:-zԓDUTP}qP #?KT[k\^SaU)O+UZ+]Zllq|c٬gsE[UW]UGU]K[pլM*_s_ZxJ~`M ه3jU\O۹iK∁o%NWوx<:\qJ^8Uĭ$uCڹuq6 ^Ii;.ea;.[Vw^ fCAzr=v?DE: {ё49|EYC*yQtE,w˘%qzՃ]F:2O:"&%ccRג0(??s ')"ElP#Ԉ֥["J .9n}KC|c<ڒ&LsWcS7@NQsG)S 3˞3E*C}Lg⮜s<0s9Mu~ғ79MOc˜MU} -$ $C8NltQ" %q]ۚ)PI.'Eҍ"j'>C ب0x>%2}Ş5g)jORXCfPi1RZOX̶'ꊵQ6r|exV]!wzFT-۽kmw1~OB,,.̿˜a ťR}exA`gxHl\IAB{ Up?#f{I)Xf{OoHJ%BQ*Q/R8E%jd>Dsui },[?-#Q+4d+?3  $ 4TdA$8 Yyl1yӈ B AT$T%D'"d*+Bň-B/D,1$C3A/24Q 6k0 &x`Cf AA)[BD8! j  S`lFfӰ SET]xh Ɉj( + wPRb  N>PMVpd}ֈT a-VQPvll]_Oh пd\fP8T֩PtCh0` mKT` VcWRԬG!q\N^UQv}[+QX%XV Wx{XkWhU Xظxk_̈U [=m؍]Wxu Xو *7K& lcl4}AII,LhK`0À<hE&(Y+H M  #ZtXj r ڼÈGY= ýK]Ih[\!O V]ۼ%U= J%OJt]v=w\ݓݵUE~P}%hmۈΕn]sUdXRujg]uܸQ YL^e^}]=L]^t]FE ɽ]̧`kl.|B 6`ȆCĸ}G ZXpUϓn 4hAtǸV\]u\ ̃n X]pb(vƥ$Og( `>xB XKZK2pɼ< Hʇc9`G>?}cF =v]aNap@(r DaP,n :kPEi}sN:$vYgg3~Pn%I0vWEpvcXtVufO~mzNf|3斀\$dWԶu^h%flXddc&=F Vy֋ c~N ɥi=i FhhZV6p@)ghS$TVp>PTvܟaVCHPT{6g\S]X\l^ܼRh-Z%h6Ftgn~k,cRvl>w-j®i+|F c=6gʶk&ȏ%Efda +TynJdvF ʶmϖ̓SFp4xKT< fꈸ|Fm`m=h=Un_>p"t3lJo~Md_^j`P=h O .nnF m!j0&.J;iF ?aw_pQ>$mw[Z%`8Z)|ARP`5?GӽXBM0?s*ufѫZkf+ذbǒ-k$дjӞm-ܸrҭk.޼[/.l0b}M1Ȓ'Slye@! ,! ,! ,! ,~A^'ݤ #JHŋ3VD4iIɓ˗0G!~t&DHgAɀCH9nL$A  ,M PjժWju!}8uj`Ϸ=kX60~n&K7*l%{ߊ ׯrN;"κt5Mz#SLgІ[:tpҰf!#wH.>Y x<߮Kߗ#fN.&l O>}E'pGrV4AL.~ Z3{ .HD &4yh`BaG܇;28y8X1E5ga85v"C5!˔:VP `fgrV N? Ł3 {ir@Ь$ʜ:j)yG< [Dihy#VTj&!X`Y8MDy4tfq&SR%KN K![5 γ[]EE$.Q:L|d/ YQ5U3ɸF!ԸÀՀ%! ,~&NI5*\Ç #JHŋ3ViaS i!|"4#ʑ% yGB$:UД)SLx+ƨV}ukUVA-YGk Ml:n}GFeτꛤ:Fvo]eVI>b6d_fKVT D]."8^6.eDk[b:3mܩ1EkpϴܐȰ%kRwc=*nsQD%uKܦ5z 􎗽or[^8 ! ,! ,XEq H*\ȰÇ#JHŋ3jȱGLHkժ+iXk2c(rUcɳϟ nR HDiS7*թUTj]8[[/NzEy⩑emzJܻ0~ xp_ C;lg13ULºjv+Ҋ;Mtf=ȡ Pqψ<,/>'S8:mjGG9:.fdZJU7^+w=:*cHEqc±}j }_DKD5C&5Qv,=Hف .ؠbWR$Kbp 3Vo? GP3M5!0 & tT9EP̱I'ށaA^yk`5 MbWАaX'Ddf9P5gF.y@O. `5<@<5g Kq?i< 5d0sMAЛqRC'w?}Y(ࡉ.裑NJh>ij^3DXaq˥oQૉȷO L߲R(~LpOcAADA&H {+>YPs ׸XUj1?0 aAGF;񌊝A h1N)bQ\b-F;!Ex)DbxDpюE#`#gɀeq=C*M5jB2.cxJ9~娚"tTi 8Q+,1 =$&##@lUb :9b H¹[Vb39fm Px ~GQEs嬁29[S29(;6MpC~ tw$; <*Yd ˮA\2#]"Qzר p4ka Q;Pj\)UB* ĤJiKR q\1I hJQ⩂P+-!N-T}ʨC jdδ7iZRC=jY!ӥ$Kd & iVhGAkDt5@11"La)># @<-?*$C񕐁#:9E %DFȠY"4π%Ia /ËȐRJ "ZBF\+C2*~ @*Q a"i +@l{!ɂ@0#xN BX́dz{ssX mRIzw?rcV` ` E:JMH#$F.D\!)^q_7l3͵h8=n.YU!lߥ`֐\>"y68 WI- q2<.EDו٨Or8Ytv}=;C*s=A{AJPy<̀ 5 8<:r2fzVtfEE=8G즦!}ӣ@C  am#j0 !ҎYWr >*| npWt$"i i@EΟ7[?G߹;q 7 $Dmr Hobs40R1DQ1 vwxzd9vȁG!#ub /8v !f~t:؁|s49#9Xxtf-;ȂW1G8dw7g1ヌwvEjZT8@ܰ`aX8`05jT z !1ŠiY { mzAk= tkkM{{ IaIGSDSɇ\\0mCm#76]d]n w^qaps065^FP 6p~$qGMxHgNhPAb+y P?(01h5W 3U,5Pd48( l0Ix|> 討(hxwCP mkV"qmqPȏ)QIg(XB*xy4"'9IV(I3Kz߃:  oiS$^V^#EX:>q^R9  ]M8x9)`qr(rY6\ԍ>rQ5t}[CW;# (䜤=p G]wL='iکCiGAAvK@I94yyNI Æꉠ왟 h1 ހp BS"J&L5i(l/FWtՠY11px"yxWA(Aƈ I"/RA0 0 \\ᖌ"ZBRmDQ7hLOc9LipV>DL _Ki 9K_֙H8 dZ]Ȥz Xb8 "$ iG$ `.章hϐC>H@>v 뉏YdЬzP6隭­jz ĵJ>#1 "*TU IZz뮠b /x 9L `1/'ల C/AC3(p$. `qjwG|8 1-A VŤX9D$F aRS ! PӵQ5 &K5DUQ^H> N 6w 7 5s6(z7z4Y@~7 ;L08{6иMgé>Rqs >@*p(EaAc1L"D%Kj@c+ػ˺$aAY˼ҋU0  m",)P"n]M-mP}]/=ݿmc}2]F^)A.0`{@. 0@b./Lה-B.}J=㵝է=9~"$!˫LNPA 2`7pt.p~h.޽]b !4]}l^T-}.~!mq$N]4 ߌ6蛞]=毾~ꬎްQ渾Na .ЉEM`4ؠ ٵLm:.]8Nu~癝@.6*/}`Z$_x`(N.P/ !!ANNgyamhmUaOZ=Yh\Mn]/z|suÅo`AhI˖wo=/UO_?__/ȟ+o?֯1a1_?ŕooh/QpO@ DPB4h0QD-^$XF9vRH%-~4R%D5-]RublIL}\?CGDi4dQM{DaҤ*F}P­fڱɮS:%5,ڋg#~vZPÙ%+z_gYuZ=\CŠ&vlìRnJc^j64KK^5pPdv Y ]r\-l&7nx7[׊j;}9VNPŗߎEAOiq7^o^N;bӌ< O?ڋm#?$4(/A p@2D$o 4CbElmh7l38㸉nK\%/sY0EtP'=+ڐC*lLĎ.2C1Er3>.M3\.8skj<\pK!4F5/:D3SLD8 ωh<։.iG#{ $lxz,tK_GSP,MX(_UEh :i /el6M37rjtf+KS\AxT-[?WDW$C),AhG]%Zɛ%Ai''{v{+/me-Vf[ViU4YPt__ޜ!sj3Jt'7 ӊg ?Xg{ISGW>y.z'dBqxupY /gd57%S&A3eẹMw pּu2ʀ]ZֶO4 dFzIiֆA9 RaTS zXXuH5n#"]bcsV|u6-K!0ר5Cjܚ(5&8QKSn4H-y'Y7Υ?FxJJ)򁨘mRSuTᴢPLk)"J-|&4l{f4zN-'fԣ & ~=ՠ+ljT2=V*[URȄI MWԮRu(d*:\bG]j\zֵBuw^SծrZ&bNXWˀzE`aX]/Tj\ػxU3N$\ZҖ)UdU+WӶֵmle;[ֶ]mnr[ַnp;ָ1!nr\6׹υ.]V׺nvSm׻ox;Eozջ^*żo|;_o~_p<`Fp`!a:pa<<",RLb qE<88!Q{ o0eN}0AjF?A5hf7̥9@ @Dqid+Ѐ-4j7&a6/&Ah@&x85e Dxvp` C5Qv'&H & dsvUqٖ7vsYay@99yhG ̹h9Õt08qW\!/w h8Gb1 J : +ĥ> ulY yXvnw@䧩-#pRs<|7CEu/IP!n:ܦ"p!iF`kr7@@Q]|W82eN{ܗ/~B ܰK; :uN,w @isSXBñ[xЄRsx+k`]#47h cu#kökȂ77"uB< 6-

& jHYHjt8k9ӾHJ 뺊ܸSѫ`5;=ʻŧJ<=Ȼ|K@˭뺘:స$j0u5tEC˯LKK|Τ#XLlLJ4פErʂ轁ʝMͧq$ӔK:XMtNXT?K[ú˿Ht \|^cTc̹괂ux O|DAw8XωA GB>"dOmKԜG;, E7й=54eň! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,sAp2)\ȰÇ#JHŋVРQCć CI is˗0c>8sH@Qѣ%op`ӧ[d8ՅR]i 0hvT,@۷pʝKݻx3߿ /Iˆ'6D n0^~.~n'()1 9BhĠN 18@#rsV,䟈'M"&ʤބ*<ΚY Z j+k&6l<+Vk^vKlކ+ɂKzkn++2{I94,%l +,+k-+, :pk\S18,6kTNͬrՈ\j\C?(xӌ2s1zsIxӳ6skxsCS j17*߻t7+5\4匍]{\vSqomwj:*cSoCRRp;>cq3͔`,b8myc;X|K,[ {zz5ԡ7TLϔX:4C:d;,,:COek/2׷Ӽ^">c^=ܺ>+~j`c:@vӯs,k&Ukht`1~D޿,֌j\t⊥4a!0fG-Jp#Ě2HaO"+ѯm2v5V`Ceh(ѽ5ְ͇QkB V ]c #\5ľ-HPczc70űbpg,¹eK7A= !8/i5K%GLzZ )Rb˔DW(SJk|?! ,! ,! ,sV*\ȰÇ#J0C6>ȱǏ 9XQ`!S\R %ZʜIc Ѩɳτ04I(KHO0ʴDHwപU$ת4:(xhҀ…"vKݻx˷߿ LÈ+^̸ǐZD13pE9写7wA 8qze8o >cpѯ@[gAN_˕` X*xJka=E18G\t6h9g㙈o5VY 4PM [BL]sVmyB4ݢ?Lx!t ,"΋/Q70jzAaPhxĐC3H.7,ЀCO Dc?w :Rȍ&٦c5R\Q@rY}F"!B/)Jxgb548?ĤXiTtCnhR 48&;"(%-䧊v@s7^g~P:6*a@0"8<C8Sr9x+qs;P+>+4bMc#4ͬ%TOV:QA|H?ۈ+]6(DI;56j\Pr?Hs/aM_AHP4ڟ:.#ok 7:%ؔ#+;N5>qm5շ|)4vɇ9;{Я:뉣x& fN@gC.s%6uSC3V6QO|2͂{J`H2а!E氇>H"HLua:CVJE*AؐN&\RQ@l#Ҙ5n@^PN7p^:8ڱ.FC~aohb'#oIֈ6U%QBÐ,(_:y50_<"JܑvpGX|1c}'!+L͏4FC\;(hkfc: 3'&mLf:s5wv_C\$(H;0% SL GwԦ0i^li=,6K˨\)P4AGt< T5HuE!lZNm+ɲhnA@Hg2hXNvf]M-Q!,'CUwNFX`'\*n-]jAYY9\4H kb%8?7Bm[39aQU|qƂ|p%} (^C!l7 ~\Im?_0 1h_\Wk8 3zq?>TEΝ PYHGvv]P\,P';I?(3?N܊j ;NƓ/j9z~y_n: rܰ0ܥ9P TЉ4QIPlb .{ᆴmkR?ٶo[ԯZ-c;ܓ/N:緯>~˟> yһoE H׈@_@& G'|&`G!1eRd !)F# D@iIS+HPv;=6so'n (#|זZ9DZ׾@a#a~0L>jZIgUfNٽyT}^;˺wEVT.󩭌Pz+":Y?Pjh#3ɥBaNhG 7KbTT.{Vֵh(9`ַ섥' [¶\qZl]=yH `$S¶ 7.,%Lwcv-]xj7"}1Ӳ^w%LۓnnK+YĿZ1Eڙ[ 8q'L [ΰ7{ 3p|8E)]|Sz!xR"*'qhjǴM<]L\9xcYGRAum|^.l9,5#V&>iVZdJm#hN-,h7L%='ڥyT;=4+EKg~Xg7+נ$? irԞ,jU&ULհ6u%^0)WǢԍ Pq|%9(i8HHqGc$|c"9 9ik8.nuJPuW.q7!yaA>,A%4I5P`\{AwT DH\nlTL(և}xxWcEӍ\@u|RXk%(r9׀0YEX~=9;74k:]Epm5WuJ"uX*U>CA=ytט]yWi=')4'&(鹞9`P uxqz"0{&&{J#0BBfKhv$]MJiE|Xf\]dsQjf))@s3wI^5%/O&Z)v2$=?,jTtr~1 p t aX- hJJ_I]ɇcJlڦnpr:tZvzxFcp4GX.&§f~Zp7:4m$vhm7eacFl適_f3ڧzO718I c| Yh[%|^epfD勄Jp=s5 ^? >i`?bJ_֬ 銂:R` 54p0q Z06 oS  `y:Jpo + Ip u@zy77{G}*S(sG*&Ұ{=K M!#&*˲VG4[&*Eg9}6vefifjo~o7@So`6)SgZ(+GrV[|&0L5N qP43]@ I [b+@55K<;@7@T4лI(x4`p+&4˻;&ۼF'T+2#k˹ۻq4Lk¼΋C;ڸ9FIC'ltT16eH9} Gշ%:<<6 tT*mO7oV*U@ k8]JS7\ JH'u4кT ;X,|`tl+V,oANv\yy)Q<şƀ `4 a&cqd)fj&V&wBdž#4~ǎoLy@{B#N ?A6m7Ī[/ܔfBB`$dCmB CHU sk%H G+ l5@&`&,3R` `&]йx8ƚl+Ў&X0-Pr΄aBf4]c-&&z&ζB,];Y|<3ƶr+ eYfED\̀=nKJ`ëpf$H&8 }r'1-(:@ ׮״0SKӦ%|z׹x 0%-ٔ&~ ؂]A&Cz |]*&,pq$#yM2y6o8Q]bRsT|4,%') ܁b`& <]㇆G>ol tV\GԭkȔU&[Jt{Hr^VJ kp= '&,'P,(M܄RkHȖe}q'%=^ɗ\D~G`l]lDA+%&x&j~8|&ׄ>{$n lޝ J_('~,_% -i qDHj*l5u]"ӹJ&kQ3|-Tkp@pO}5Pk":kBT 33+:O0/4P N'M 60A+?3 Pň 4&O_m)4`j@6i?ŶBV`>v]aW/BZ%,΢w:; LZV?f_ʷVOKzО y ;JO z /T|p0'/{6I@"ۮ'?Sj ]P˿&oA? ˆSi٠qc)T*t ǁQ2qT5Pd4@- $HԿ@PA EЁ6uhT?Re4ӢZvj5֤Q%+҃jۊuk(۲b%:]fݻ6oյ|=+8Xăٶ+ƕ-_ƜY3bIkcfsͥMk΄4Zҥ?nϚrm;7ս*ۣ2nJTm{vztꀛWǞ]ZInd4vSg^;xQ3]kz!/u3S $7 >Ħ*bI;æ2n|Ц1;oC=OA*Bj;5ưf1Gw4pB2H!$H#D2I%dI'2J)J+2K-K/3L/ L3D3M5L6߄3N9礳3O=O?tOA%PCsdPG4RI/hj0q@'SO?C2UrSPOE5UU+4!l*~HMUwW_k3r N\3S6Zien)!Zb;oi>jE7]u@-f u_Gm*G$#/q5{a Fx'ZewY|!9d$zf9fgR^=dUVՍ3i&haJ6/wg3hz`ziŽ噬m^Z:m܂yྺRm˲/mfoKpGdn!<ŵq/y偔 ,kyF "dy p= Dh G-*ha@*rp>[8nw–djaBe 5uu;A s 5AA.` W | @N2. e8rPANB8Ԁ`P(hA ~GZSρ_4R  Ѐ ؁iA⇂X vI2@cPIHC^ BG;Qyl zp#83qAIe~q0rtI p''( rJAQ@k_}o b BgA"a<" 6×d?p)@^~I\9] d &įh XqUbZM`(A rpJgPDPuN@+Lݮ#۩8d'(Oi^RγůFܹd6^s:ӛ$eN#Y$La D0 5ת@]'XM`/7S[ĀYd2Vf6osCs;gBЇ޳9D7я%]OHWҐt#wiNwК>ﴜG\ qk2(j\iDArj8āI9MvQ7׾) l4OK~E Epʆv MIjr{RFm Q[):Mdmoҏ5pո/up [&dߤ3<=@J;\d%`-kgHɃ?1 O,80>J`)_yƑR楁6 I^X"ps/I蚩Qx# E{le&oz+s{A܀<@; x`k\n0`oj"ҧ'4H3Of\υU__·~Ao+?rxw#3>Q g(Pk3C @뿝@l(6@ >b9@?;A3? bh[u&!HtXn dv@c`;'B)l_ j@B)!Pku@vulKAsBk;t rXKcp;X:npx\p_?³~EC ST`;;!(="(PUQN@&/ ř˹9=PP \/  B؁FnY? E9B9Pgԃ9:f\gFijFlmqpor>!n+DIdI;IɛԄI, ʡ,7D +ػɖJ <ɔ\c D 7HK $˚4Kr\FJ4J˵LLA<̯L v jo hhjx4I 0(QS?4躛jn85+>9-:-4S=ӟSA.캖ӧSbSB\TSK9V*CLX}DPQeXF}Tն/KA]ՠՆ+L[Vό@tD+hr*sEWuO/hMh$eSF$]U]]8u݃ddPe(SNe_f`ހan{j`cf ąStfnc^ekVleVfw.fP]d&yOvg~D ޔŒhFj`v`+0}MNnՊU;LsXa(xNim Y8YP{ :b]HY [Ƹ =F=jPǪjlڼM5~AUPbEu굦9>cVG6F C^Fa4`~ DFd\ hp}eQxȦWӥeֽeClPledžlxAMf̧lkFEVE V6yg5gFŶpN_z^[ XXX`L nXdnM`Mh*xhנ(A% :?F \Аa *8afixuOzěЧhŞ6!>K؄]?O.gnH'IOttԮgbxvDMKc^_䆅n+X\NDl"ZƘ; iv9oߪؠX>c3W nʃY/J,EP ǠF!(q-r8c/ȂPǏx_!qUϱGыk k/B>jh`%؄`5l`G,MxLzcELcPsesV8?7ToɫzLz\@̛o|xtZ'\>|{hŧw_ǜz{ svCPg|6|ȟ{W|uVCX}Go@\8 ,׏oRG>) 6(`gTux;`<V#t,Orpw׊U#zh0ax~ІaOw|xyG bByYeঁ"HVSiPVUjGܨ(gZ4c/863gb3k8,sVFA#3UiJ.aʤiN=sE(QHW)Ũ&j$['*ۼmߖ?~iũ[~ &L1npELYjХ#Wfjz<zc2?uF\4@{ c7*Ә̛̛;&gQ^]x{.ŏ$h5Ի9>zUv-o>Kzõw7sG4֥4٩DL;7 ,ThN18bmAN;謃?֠I;V! @Ll(:R^wWILR.K:98ps@,P TCxS@"5#jQO=4i!zy`Sl3d")8%D!4ExEp5҉G'lIKP$:it_fqGzl?: !>`Ȯb(QaM4VYLM*0A؊- ++ulRKNYQe;pTMt8;Ր#8ȧ)* ;0K>髿>>???'< 2| #( R 3 r C(&c b3L./.!C1b2yD7C͗:R^OX =ot7_yÖp)\7K$d pGBA9+Aթ \dFE HhB͐7<D$8KF\I11 )̡PHU Ȩ!JJn x%T[~%O1 7ԍy YdLCLNB_[VUYKː@YM@fbd2U.H%UA:%E6|$hP$ ,Ԙ LGLiӨ`:l&nVMLxt 阎i&k_rN&ugn mFsZtfv(qZOur)xvLu"y{S! ,! ,! ,! ,! ,! ,~A^'ݤ #JHŋ3VD4iIɓ˗0G!~8sx%B? ҐHEqc"hPCMXQp` TTUb՚P.7 [6ρ kFz 5jBx! W" ȍ~ LVS'Q 725iAEV̘qѥN[ԛw[r5F$):ͼ+9i;v#jƣ} 8ok(A8DT8b gBo֔m 8CDT]] ,C1@d&h<{ҏ!!}@Vc}1acL AK$B\="CNyx#7V8aK:$BoB)N NY+Y%v5Vs8T#?)ݓB"TC2LEMДS< 0Ns"4+(*s'R#hbP@ޤٕ%6G:X\_c87^vTUׂN N]^ / q `D$g$VT VL2Ā^t5. pE5,H! ,! ,~&NI5*\Ç #JHŋ3VY x<߮Kߗ#fN.&l O>}E'pGrV4AL.~ Z3{ .HD &4yh`BaG܇;28y8X1E5ga85v"C5!˔:VP `fgrV N? Ł3 {ir@Ь$ʜ:j)yG< [Dihy#VTj&!X`Y8MDy4tfq&SR%KN K![5 γ[]EE$.Q:L|d/ YQ5U3ɸF!ԸÀՀ%! ,~&NI5*\Ç #JHŋ3ViaS i!|"4#ʑ% yGBP. -3D03BIrG=Ciy@imP@=Fz ( PݠG'P{oD8-"90UC8܃KC}7D5C&:4MNR=Yu~X=W0H.]AD$4"9x]Cl`̮vH ȡ/#P&:A P#557o0[AꐇFfoq. j}<5 5`0E(S# Ϩ"q>IhE%[(F4glF8ʑvEa rc(L*BɁh_"xH3b4"'7Ƒs[4TewyICSg4% jH\B򎓴.[c԰? tX!v8y5j״Bu; S[A8"07:zpc܋@~a o, ,Ǎ8 āD]x*$)vAp;ۄl "[E%NTM~@^%p4Fw(֑O"| @|@䁂;]`~"G߁i@#i]k[*U굮?ټ5? ;ĂBiF&A Mx C 1#u!׊ q5Nhp8q]j‹31

nG2` I/R 3UCg?X;S+'xP /4:A @(t 6*1*8 MD),+ a:Ip fЉJ0֊x*p4EvSD|V]5am:sblgҦ?lpCnBܩaF<@XDŞ7B$! !kKC:)hQY. f5IߢQ <>q $79jrICعI's)Jd߂t_<'FKO@odONt a-yi`\ 5sh=6K^ tt & æVhAkٌw@l+˓)># g [a -88`'ɑNb 4@ {FH n`&hz 4P "ԅ'4*Pna\H%*qk䯅 Ժ2DxYHz Dњ=,A 'on;0Y U۷4'g'~X6Zp PP55pHTtqP B ε0(4 6nQ QwUCy`sLMEܠ8ewFJFHх5`fbg98P @s`s?8uo giE YCXNÅ|(eՄ e?Sxg*F?>3!ц=]mDiCvjx0#= R4 t]8nЊ4`yKy`\r!zvp}zK9g58@7{@6 a6 OxR4f `B &_jss`m2'~ZV5E~KSU“Wq_ZO^GkAUX5CE&%u5 vX(! #YMs   8 FH!EA7 V p]a vF9T_`X4`aac8eCKsw9^`1xELy^ 9r2t?z^Ixٔ76bhYRHhJK1YytC`ibyGyr8< 34e5YIGXfhzhv9\pf !gRAE@MHa g(9q|PCPjAjEE耫4wk3WCT ci~k_Z: *Wn9i$iY'YUʡ0;"j %z@ihpLYL1>  n4LH5 cN*6aJ LeET:8TjyP i?Sxԥ!1ljtԥVfKpjeʙ#痙hKI|jcVP*ychZgxn`zx2dvT $Z8OeHӉxP ]GFIBxXl1 ia i1U.U}F}dg':0Ue~M~)~ AsT~FP P56:8 C&ʓ)lJjf$*:ړLYQy DjIF IjD454d`S+;-+@5Qxu+;-/+13;5+fW G,kS<[QW@tvQ _SIaKI+5Z4wPZt_Ka[@c;cX[_O f י\@&eq$^5Q"3 a{:ǚ7ye i5:s,ح#TBѫ}^P >R4&˃~~;l%E` hY>^KSX  '~;m[ƽ̴j ď O ހp ԔrLȆEjNJQN- Pֹc yjx*ƕ䠄z]4A a  i ?77;jfm ^U6CTU&$Y ul[-Pkk8X&U#υՁY;( !ΛΫ,k\ Mk;;  * & h15 [0҇$%kd / n /3\E`ө2 ?44/Ӂ ǹPӧ8> , L@Mm 9I9\CA]C2}79O}d>Ma|+&ֻDf ah-jӳh PM9U 9@ #4L PŠM M+}Cc& UC@oz 1P!+ QZ#%d6Xqڅ:v ! 95܆S &!|ѫ(m4E{ KuY ~1I5tGV YVYUkV U0z%mP *Q; -- ){ VR@G4>^Nd= yƕ0K`A,8xz2H71Z[@ Ҩ-sGs']5= V5E;]^,q )ޘPgN9>fi @! Đ̀P<O:9M W7s ! {EkPn=Sq8cuq uCQ  ! 6=&Btd~2Ҿ Pn'׾'~'ߎڑQp9^%0+ ^Q*12`+b#*( ,%'%/ 2pn2-p ;! !INO7PĆ':o6??oR~#_A$!\?>#Q!oM:'5uZ}oQA? A#/#!B!+Aa  a+I#VO/ Q5#qPPߑ02a?N(H 2 (B ?$,8FB((1 ,0pWp ‚ o| ADhp!DM8u̘reK-&Ad t(ь1CnZJPL7s칢 &lZE8ȍ߹uv + h0!҈X<:` bED/d -lđPhႲ .BssPHs%8#LhȐO O3;`,j<>]UT=2V@(h)D8& s=ïZk6k#* :OÄ* T HGO#Ҽ# qo`*Z+P .(aވxYvaL2Ȇ` r: /}lxG-(tfu-PÄf8 ZOMLM+&RTq߃ox?OH7}fBt#T`WbJ@ F# 0(Q#Ywe xQ Zd4%j^W -+Fgo_ׄ!WH,=Ca U8n%$6CO/¯ =͙:>sR3^AZ6Q!O40G:K%V[%ft>/uӝN#2?I,~Q@rgӽqbJ1l0 ̴&;̪DVu? ֠*VSdM09B֕C>/ Z4rVAӚ\i_Ԋz׾S Gv Y1lAkw%dqi樎L,@!V#(U- }NLU "TdS)Ahӱ@6f)@bA/j3D ~Sq.3Ś ̬Zt6֤^ҁtÈ{ޱq)(s[HvN/`Èf'l"J 4g ߄H8-~bo4I (A_EWG\Z#Ze1RhuG'll%rB v;bܶ1I,tTdvAJ:T^ U<۲ t6-@ .#AY99pHk*w~:ԯtHhIҖT[3 !`tgRNmL *t 4zYT Èb\k1ƶ!fK&N:#laFXfwۤv=z:ؠvwXzueɺ `t k㐶gt[2E>j1}] ck$ eZ7y#7s{>7 J,eتbPZNtzֵmoz˯=r+Nlro{>t}!{w| "tG||%?yWq<հyw}E?zҗG}Uzַ}Qy}u{~[{z~|7?}W~W?~|տ~G?ϊuPB?<3D@5u(50flΓ@ lfhDrΣk߻rX AAC Sl@5hA\,A&Tc(B{B5hBޛBлB3DCS#\C C5hϛkt8ѫDvTJKXeA ]BW=RU]ENU9Ru7?KZ [ՆHv^EB<č|ѓ,@%IGl5Wn LE̛@Pz)<ћd@lDoðE?@b-B-Hoh@#$PYX{Ƈ=~l@XˤCZڬڮm>Z۱5۳=%@e۶۸[sۺm۹ۼۦ۾ڽZ%5EUeuDžȕɥʵ\CCΕ͵Э[Mх?-};jҕ]upԕl^+ޞ;^ [-g@q^bpq8^_^o Igέm_lqݎȃg͹ <5M_Ӆ  ^_{Ə`F_g_ϰq S(N\"&5p)bK`b#F!N_hbx=.b `+f`" c jh FE_ե8uH-0&!@C fogXa Hqnb@tp u@& _X c_X~bllF-.l&gҕcm` &u^`%cnrF{N9|g.bHg|bunq>p~gqg/vh-h!qgna8cH5΃6mdPhj闎I*g{ A_[ b SXf7@xha@hjc&C_(Hkag`tCh0pnjrhMc"XdopAftPo(f:p&نnuAfNk._s)'vl(rnd~rhw.9Ȯ`zvߋFw4s.ov07ggv)wwӎm9v\pHo@W]wmaC'ooefH9GMR^IOJp$^k&pnRr'WgTWuVwph poA`:0hbsNw`WGvwlqyw4wpz ^qzqv:wyܽq8.O+&{k7rx{&glwsbz s.|F?ﭘ^win߉?uHp8.ipyJmj?TUVYIuEuuٽۏr-Wm&oz(w'zzq%?O~mi?tw6o?gi{f2|voү4hJ&pÆiL4Q!Ŋ4<2-_+I-g0.HZ44=HnΌ IV 13D \ä5-[,91M^&Yj{Piۊ}-޸pNdK#Ew5-LR/6떨Ž#Tnɋ39|C.w\Ò&nxczgZQ:.mΩuˆlZ4Q{N9e ?M̞P?FEmP ?E=Rͺhe$1G"fDDp/D2n4 S8e5MMBMXARu0A[(rKzeaFYh帣ryMZ_ĩaI dN2ij=ir>6A n-M)eeΩoYXI'Hh~`jXƍ?qS<C8Cɸj NN~/$G4V xC4H7^754T8NODC#7hiЯӅK57jFmA\1#rsE@0,8)j=v%vyrӒٟP[目gťhI㕁Rfqk>\0|1 $^"9on&$C7&? 7k3=П.fW'~:랯:˾0~;;; ?<<+<;>髿>>??^9(Pvgcz ȼ)SiN1+}A rw ^m*&nLW $# s; .(NI_LQK3ƶ& E)rS 4,DyD.7ăxv61b5,갏/Q.!q,kc| Uy$ZFSqksZša$fXHe~s,Vf<({#,qX$8H84%/3V%X9|(1nX`.41D" 7U"B`A $pp1 .TV-'PL fIINs/*p¦6mL-Df6Q٬>Lyq v?D8P6ʟ5ȮҫL -]D[7h+ZԥҤi0;" I3XRU|a`F 7 ($}QziG:7,T>B7T#0HQQp*[P1Ps#nK CFhI6+IIXv6&Ј)"RC^*_A2&oWuGwfָz -,ZTD !3RX6j@+ i[ci)ノ!aBfRdD=>BI 5W*Q >X6O3`k /jɺ( ČLr 촶?1 tĭ ;$D0E:A2W1VBa(H ZW?rLУMEo|R\737Hh_%4KԠ%&!2X=p%{)"Ru!`HL=_D#e$6!Jl Kv+W2׀\D`+b܌i4=- *ӟ</14` CwN%&iJVJHjWcjM`"@$Mm`C k;:̜@o:ɬ. aH}r=45o99^okS}fH%g>sc&+$ÎŻ3aCDV_Yiǟ5Ѡ=~gdXCߍhAhwN?6nxU'k%ȘN7f{>!Z=JE hvwnlC"@ Œn6ο=.1#*} psLfاSDz$Ca?x#A^HUJHeăFqE<_\XVA 퍅ȖIZB>Dʜ1_P1i@p?3U{aܡd0Q} \|B}~H9@ȋ5`}G~,}wu]^4ȃD-^rG^f!YH2g-MvwF@sӑ-A nVl"&S +fc ΢5\=EL/ҳx3x/'u˷ F!ą| F ޑ5~vMQU(h &:/z*"PPEX%#|lYP1 bx,ܙ z7LjUI:U:Y^K75%5/gY-hie75cf~t9A1ƫqG/:r nge7hgf:.!r;qn47l,G~t =q'۸2#g:z XǑˑeJ~5ˁv8b:y|/꫎t}gf:{5\&2.!gGz?_ѽՐpxuO|߾<8PN>_ks-Ow? ?s˷agyr~jtw}xftuy7_ IG|Ɩe mk 6 }'oƖc 3 DudV}`}7_vJnswIV{uis9 Vp76pAndI7{jd@o#XvPdBhu]j~idfkAff &n6qKdGixxisXAj'x"Xkt.x_%K xnXvt`WW68`f _@~$Ɖ`u_a؊8x_ȋ! ,! ,! ,! ,"E H*\C#J4ċ3jȱǏ CIɓ(S\ɲ˗$+B #. VH1@0 JѣH*]ʴӡnvJjCOjʵׯ`Ê(oF Xi0*P,g= Vܯriz]܉˘3k̹獏 ףavs<~eK(fldƈ`)d* Vxyġ@޶ApK.b.Pd'} g(~@P@H[7ρG XgZD9HJI* DY!AX pG HEDpo~ @JЂT,APlD'JъZ#_ԘisL (}Lf5.Jw 38ugNX/?Ӣs O |4TωT!P@LNڬB*9u ^ZO)VU w[еu+MB IKʕi`h,P:D}dә"ʠ$f9g&` hGKҚMjWֺlgKͭnw < 0d@(8s6z`>k7  +>2tK߄]" : XӚo}-`ڥk\W!rΰ tBd"` P3?t)\۸L%EZ=L"HN&;PL*[X2FY.CZ2E2f9KrUb` Ax:k||9h~ruK]6n92+Bo|%:O&oCȑK V SEHԧƴ;Vp68Y _R2N dUOyOP0,-cDϼ\@ s`Z0pE-i+Abt2PXy0Pm8#A pl|[g׹7ubco7 @ Mˋ7LcqxJ3Qrhag-4Ϲw@ЇNHOҗ;5:QY6k{V߲ԟ񍈑!['ɽY)zdm>#_oաPt/5Bv wGC`"w|=x }Vcp|֧Fowҷ0Gi%Xi}-W?C?MuDd'0@E@LfM?zU0/D[sQuXZ(LI# {ifj&x`ׁC"xց~GvloVwwje]q04}p$ZM"De}zsv Bou_qzD71CwvV4pUT4chinh2fWooxkX|vv'`O}6DaX>KHgc*0d8On@!cq8K?4 @f%ejȁhpgקG}-LËEh6H'hjg9T@!g!P$p4?D(nHj(xoȒcxkzׇxHAt=aуG'ͨH`T'3TNX'?1gၑ1H5@e$[Up~$oJh_wH!8osE.3*W2Iw>?)94JmHg6GId6)@ֈ4-'5PbJH A{Xg$firk  xdw|9JC>ًTKK瘘KBͦg)'uczpvbq$pI!)* oZWANt lw}ȁFʷj}2Y#ҡ)4b":#$2( 202:4Z6z8:<ڣ>@zO/J-NvE `@ Mbd]l6 7kzYfWSz(I 8q ka:NJP*s*TpUv^w ѧ Qn*aڦxp@`|*L7W{G:Fb{q97kfgj:vV@ëu&8c99SzS\?yיjD|6q <0@ rJ ` /抮q` q}*qj6 *px q:~`/e~sm~94ЀWD%#3hgdiK8ˠ~@#l{lZ0czDHlS5MYUAx]z ;\fƴ g{k|Hs&ګ+1|"c\3L[V~Ѿ ɑ+Rl+U:mmjVlkaŀi߄D EF`\Ò*4` [0 K%u|z(|'"P̉6l?a^Q!",Da&<E\ET@ݙGp;ʡɫJ Q"Ǒ@`9`z)<@  Q1,jv~3Eѹ'@y QT=7 I2m CoFm@B>D^F~HJ CGZOm X,s|9 _g^j< l%a ej[QbSD7 6c^ l!$B!`YQbF y=B7x8àfj"ER`R-bC}4Ԭj~RMywyۺ: G0m ! h~ p ήp h@5. m0   QV皮 q: \+hWb%`췴/eIGW hg<>>SjfSp/0 ʓ߇<<.մGA@E N #PQ F"rP `P < k  %k۶A]+rKv[>m+pdd{G(@,Q"-\oAU9Ҥ>@R _AĒV2YHeD0OY0 jxd&4,4]rBOg掅ņ#n@iHtA0' B6 dC!Z 'v5ifim`ɭjK0Y=DS8S( VЖ@Oߎ;uG^ 8 ha)K_t#"9tKec|EC٥ttDY$;<@PSp#(2-*2pX-H!1@va HB,(0Nm/`aW5BCЇ?b8D"шGDbD&6щOb8E*Vъ4F+vы_c9E2ьgDc$2nqoc8Gtc.1|d 9H 1Dd"Ҋbd$%9I92:1T$'Òpe(iLGEdDLJQҕ{t'FAae-I] /袥I $ e.Ib"s0JjV~$,$Kf\6I5_:t&8)hJd8k,zS誧5җ4X)i-_B%ЀNlh=̓sZڥ-9Quӣ?ЀVt5Wh6mST_҈JEEkZOBujLiZt &^úWjYa~s=lYwRBuOЛFukf5[se +2}u`JUG:[[sXlnu˓3,`Luj-*&*یB,m^;[:w.o Gf׻!)]׼c{^7|uo|;ߖo~a_׿p<`Fp`7>%<<pN"\$w!# @ x @ྏQA$̕@1)9'z ]8'[p܀_]wc`#d*RQJ\7@Ѐ,$Pqw39}tK'ʁ[PiS]P1e7;N!Z沗f1$6\c]k.8AT&J[ҙtUO @!d>)@A>P(t}z@)΄]'U+` dq(p{6B|O."}00 YIDl8MV7wE(a`Bnrz r1*wPO:veZ'4\Pi蓔ul]I$D )a YÓkOH^a`xlueҢ *A j@`] q3 <1`X`Hq6` yp YHD:,n}?tƻ2N0Ÿ:,`dl#[Sv 1SLV$>0aoHz?PLh`u`WE# OU z@JP$R`RR2ժ6&_ad # ^4J*a$g*bBgD`Fhlmnopq$r4sDtTudvt@bgwGbo}$}@Gl_~DJ ".*H;È Hȏ!+#ӚqڪHȃdɗ +D2$ÚB{3س>)r!+ 032 153;Iy@3ACI۲\F3g YaX\i`^>HjfTfEl !%^G2)&BXZa0vHcz+%bh b`P5-*-'b-c,(4VʅGr)c Z-:fU";K$s@nQF-Ӹ O6GV+0IV`KnBN[c%^+B,Q3erb,jVE-_ߪP&beql'Veb^.48EgdcN,[.Rem,GLf .tcPN6<*cRf\ghrs&_n*"gZfFy.1an>Rgd^5d8hswHT&6FVfv闆~xW怮.Vn0G"Zh_qi:zj0 ȔjȖh)j 8v膄CIjY`WH HqkJ볆kX0>Sfkkv⇮~DMf꘰QSl"Y쥎,kN.+E-ii0VV/nh+ &T4B3A=392OtJQJ<b'DnJ.JEF#4k01n-nseeb⒮22nC6O:'2Udnb2Fp9z[1roe8X-7N'j`NhKp{K8ei̻=@DL6ek6Kj;ɔqtoklB=rզ%;+ri q w.z.Vg&f/cpvn~>o? 707'AOt/tq@g_de͂;8;$S8Y. TMMQSʡ,u9+#S@\8$ImOBJuݶ-dF.bW,2)aZnC3㬂RmAnlgFf.+ggoAeCcbwdww4w.Mlid I$@ c ,@ q!3CU;h;NxȨ 9t:l7q*yjj._W-9#hv/p"Vf{G9ot;FwW:pw&w|DLxXvߊ@}ʫ=M]ӳh@WTdx ~o/L> s7wv99wtPyzG|O2bw}ovDaoqs?2?sog||̧tx:?0x/m@1=uy㏍97ny&gey[ufE,p. [nۯƫݻjʫ&gKo KBS 7yH1%)#\V u\(ֱi-X<=CR 0#C$ؖM]A3Lr=1Ut_cٲb-sѡEl6qݶ&]#$p@T7~OMC<7=K^ۓ[~9ҝ9{9衋>:饛~:ꩫ:뭻:>;~;;; ?E<+թr-?g?" `{_1)+6@FWc> 8 @m#;IP @F aVAwgsT4>/]%Ɓ8 "r8\ ݼfB1i0D&6lKj1(':aaE6a8.4r[* )L) Ȅb@4L h y@!DE%Q,# D$ JDa@Ѐ2H2!9ď$!i`H^:$dA&yKLJ%a[de*WL?6O L)WD5!Z2$%j8@l]ZNŠ@ݤ[wΜNf=E*,l?qѰ4 n@> LD xJ $(HPAM;-S2iAؔ hPUlޔ:? xhjLgZNG \61*T,ESU|?GP'|XV 8Hehg Rԣ&uUkT Y6GWQذUf,TВԋu86JMm3#hpR}j>hN oR&PFtIhA(0Ct Pd: 5;SbQUZoA`5S؁C[_ .;^)Di6++6$_2DToAtI&@64:AP;\91Kױ/ 6,%iM-3[h,< IN2Re諡 h3hQVIA_܀AB$NxFH`-q{U2ZV A#gH`B RC00t,-]`]qbFrR `-ht6/dywv݊K.:t@2ǐd~3V$n?pʺ O`Mbk\-?8Btq^+|Ϸ+^/˜uVe#J*K;qV ->uT#tirܠ1F;5`>𾇹|C?^Vي0%%pAR `@{H]ByXY{ɧwL:Shf!)_kؔ=~deܶ{A.FsmVh'4gRw&ϐ {vÂdy W4E(# X\_0 UET P H]EN D$ȟm=SXќqE:D A:ڙ ! Yv0}UI=}ZחQڙE?Qz*<@ A+O#؎?#!TRaaA!Aa/Iա^zH[}f8^1! 4">Aҥaa Xd AJXIV$e} (a+'(:#b".b.!-}XAyUQ"I!v are7NsY9e@bca(\LdL*ACDA&[&U(M Y!)>1BXC?#@BT# CS9-UlJ| \$cF(>DEZE $*F 4)tAAH$AdA%hBbB~>PPDd?ҤaFr.caB;3 שQ#àP \˵ 0ۜFܝ˝F !ڎ(+}F#ekjkrF UkR, h%2j]&vF:)rcv싄#b^jm@<X2Ķ(F᫙%:,+zlm#[VjnJْڶm}&-֭gޭ--.. &..6>.F..֧^.fn..n~{v.閮-.붮ܩf'֮(<֔#\C5T1$15hC N(9C58$LhBun~O z$<h"DDڦvB )hBmn.Ao OϱDv *#P5T&D0A5j0knLpx0DD.3m3/$pHA=8Ё:6p@2pR**l܀tBeg CD,8uL0880FL/3a0A5  &,,c"B2窌DZ/ rEH@)0C 0HpD?4|2 >qu؀ # 㨎fLw &tW1x*(PxKL X5CxC/o@1#C<,H /2k5/.4PC.2?\3@,P05:3۳@B D 3305@t/F9:53<Ӏ<ӳ>S4>3 ?@A#B<3sF_4Cs: D L9S(/ tBJ?tD@Tt>/?F?;?G;iH;DDJ;4,ADWI+(P۳POJAQAI;1-:P$:7oAA2C;1?\u@;lsAP:CX/5;:C;,H4(u;4H̲ O]#s.0wC8:8 oA1x8xf,67 ?<.p'?R_D ~>F>F~Ӿu*(,BB 烾@@ LG[@? P?DH 561dq?R>0郵£H?@(0WyNKjjp(PԢh @rbƕ<څG ?@Nsӧ]?K(7yYAOk@ \:D C95ISB)&4 TI~ f<}9uc!XE,O# ,U^V4Q7Vh~VݘzUY@GUsv}ۈ@G["P1g,Ү]OYضᮡo7>=7SMe¨RiGxXǎOtN1+H=ԁri_~)ͪvn5`e@]*fLډj!jn\b̰. ]Qvy=y@.\id`ܠ,?t4K [8Zp>[fֱU8%"[vB*"\FØ05C#XۂOtVv(πD4 IK4!.C5(>F8NskƣơQh\@1IK۞D_KrdEmJ!Gi rd:sE].ɉx'?;QVj?O UxY25ALWꅊ 3IRO%)Xd=9W=p]/9@uO2H1 ԣ_tJ օd"eВ2D%ӯ pRhc#NDcs #TF;Ċ†8ܘy VI(G= f$͎Ii'.08q ?pHr^ gl#7 ՀLd&U":$D摒qj yUXˊRBS=չժp  4#n|+ذ@U?tpRDXJOe!fV ge:+B DF^jB?x;k2?*|VXςj!9p;Oq!P+dF ^ AQ lU@t CՌ5*|!*uhz! eNIlUDWMKDd9?D̜%?Z<M?r(_9x*t*An>QjP?1nCxĉx hVqh%(743|fymi9י{#4!CDP 1%"L0̂4p7KfgRNgn,TͪmX"xx\# Ey^#&e J;V TO!BgGw#ܝ5EE1k |zA!~hc\ bȸ~8&u؄ȅ E\4:C sJ\:y7VB`;lzCtF?&X"x@nt+N:RN< Xg!x##544" (39Eu Z-YdxF֒Y)Vj\o3`aCNG!/Nkb=1j^}}4546f B}5"?ݗiBC?ؒҔu9~5|Ww>a Q"G oa4pYEhy@|f"AAfUj6Ƣ||ā@ OAV&"iO2X a2< d0&.,Jn a<j"|^:A.J@a@c" &Bv Jj V`\# A!p9P }PfNZ C p pP ̰ np'l柬BJPgVnwrB" a|P1UqY] ^eqUqq1(uqf{Cځ '!*~ |I*?x!# 02x¤\L+}q1wїa1q]:Gֱ 1 |gz.xpl! 6DP"/FDV 'ZL1r%Y%H1'ur'Srv'R'i('rr `!L~*R0l@p+2,1"y,2-,Ų--2.r..r-2/r/./30s0 0{/313' 11!32%s2)2-21335s393=3A34Es4I4M4Q35Us5Y5]5a36es6i6RKF(VЁ,ls=$8C859g19C+s"s:3;1s9:q9/3C:%ij'>0VlE`. =s 'TE&U>a@UT9;trC >=ID=GT>G<'B*E AWBZ.ҠX2>5GuU[c1]]J_4ckc7Vߕd5vV{bUd;IXa6fձ2BL)4fuD3dm?:! 8Awde* .-?K&f!$mV6D@l:UE&nUdI3W=vVnuEmuoEeC4\onGe6qi 9 ""B)YSyՉuVmd9$Y@peU, ig't;BuLpwHaj]W>@hXWconeIKVn[xwsWFoGfq{qSNBrgщBjshhsyu*extQ;Mx%gr DW e|=3M>{^T7̖\szqETy;8/ oo9e{]*Y@wf}I'.҉[su>7=sCnaY8d.v-68s9w-TL47E7y؄A֌kp 7oǘaBХ:g te" =Edh;6}'FSR5YB'$8Cbaa%whHUgExyW)h9Xz7kx쎉1$9B"j BFY-lV*hht甈y\]J=YDDa~a4A^B"_AK92] 9y]ߕ?`tqUn7AyWXhGWI:;da5x]Jy|ZE eG9pYo78X9:7إ-[qs6`a&} ;euQCSϑ/{3$a&% R_2& )[A&Y"wgP4ێTA8;WR߲KӺ;; ! ,! ,! ,! ,! ,! ,^ Hjծ" hHHp"DQȉDCIɓ(S\ɲGnR8s\iAHEDӧP lhIOiʵkɞ-$ kҥE]˶@( ڶJKl4ɺLUi\Z$ ?nFl7 =AܵC P$Ӊd\$7&hr3q12%02h_:װPl*WW.e9t^8 3M0~5k5Xqly?l4QAx3V-dQD& 54 D}DM DB&z P*$"~WFbI$:iP?zl">!>V@>? P䁒fd1"(~hoA]e_Uo?nyCsygy7aEC GFo5 67IA0c1#I*pIԁ. 1@$46DKFxa L%\tPɓ(S'IH a FwµX :L$0j/UyMQG1kA^6Ez<o^H&D4"RZ4yt4ų$rЄ֠tCUJf4%?"PҠUGPQElOEIh䡘s<&aXQq0&8q 3 Ixm a" l h`~~.:yPKPTBL qRZ%!4(E 8&G t@ iB\86r^(mF_עுEa R΀BblJ:OAKgL).P h,Qfu\k`K˱9GG_9幔@ˁ\DC^dJ+uyщ@]W'= H%t}! s/I CNȕ{7U̷粓 'xe0K`O60aa"&6LZ.o&n!hEjHjDk„( 5 p(Z̕[[uF>bUΓX>Y'rT$bȳfJUgQ,\YrXL.`DK4&: HyvZ>[MDE4BEjCeԤ!ZLEU i85CAюM LxFqiEbCPH$pP=|"Y x+~:I ;7M!5Z''rԠF5"dbjDpP$Eah8D~1 <\ *.qxxTl,8W&|m^! ,a# H l0ȰÇ#JHŋ3jȱǏ CIɓ(S\RB0cʜI͛8sɳN $`c`(ѤKP"odj B QV^}]˶۷pʝKSld!P- ؔ;@C~ )& f\v]2pUb 0(f&by6C|UF^*蠄`@NӁ(e JdA2\pE6 駡ꪬ*Mdꬴj g% 9&&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwkCD 4BDQ9T*#MhRtԂT dGS!rp ?Ղ 81lzUIL\zW jlB4 X X5/ BpP'뙺2䨦^׽uЄ7 2Ԁ5`*:z0&<"Pg֖G}.^IՒV%HgpD3F[uK:,"!,ĘE ]Z׹M-z ZL?xQxC8er$Vu}ېfE/i _$h퀅ۇ0C{cǦ0X緾QXԗ%+<^j7_w^9 d C +DlQK0TѼ i{]4S4SEV&IR-[;.TN#I@qTjb?Qfz2_czƺƯovC*a]bX*Ab9E;J #5^/˦j8#v 8cƤA5Il595幆&7ҧ˞M۸s$S"-eύ܃8<=԰NͽËA@І0|Fdjwg噬pP~g&P_2Dzq MҐ ,/B H"0(4Q1Ȁ rA!D ChP6F)TV7C\B}HP_6wB"BQS?itix¤?F 4` cA>Od @W(fŐ&Kh_A"Usy*ꨤzSPgTB\CUꤤ K5fb8r66*CVkfѝ+tFE+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺x$ ׄu!5f-~+ $" rCb kCDTIك(!`YNvV"*i#r Mr-UDg!uHj ۆv4F?؅&mMjdB"#~XE2Yz*@Ļqsqم Y8DM{2f`2:Fā!1\v&@rP\v!5 bc[a~=氉N`WCnU(byW z0dk!p\h1}#Xsj!dM\cC쇖KB `r%D+DŞV~PnB68H qU5":⭄ ݫ(֑ gW"|pXbiB4==P}Uu Q]Palvo#ёt/MwӦ5-}UXVqq'*o$d1B&t rH.h4x%oW8&p܃P7>3` Xv"օG ZXa9d| i0 nPЉJ9⊠3ccρspsנ~qEה/̍@s\V9БjϾ/)Sr4Vf:*]q!"_.xg\[1uW#B}m?AǶRPQ]o/6Y@ȠassӠ5 &;*`,Џ?Px}o>oY~llf6[yإ{ 8. Bh x~'p xc'vcbW_pssPs2zYP\y|E 'i&[&pd];c(|Ft^[`l/Hkt{P-VPqj{|˷ %Y7 },j5i30W[Gs]ǃ@pGc!0`^hzJv*֖vGwWobo o(m|Հgg>@Sq݇'c!s b^مuW]: Gyi@W\:FF0v Whghg[hhxjȊaW~IaV@Bf~mecfmg"WD{{haG eV]H tVT[ xl(g(e8Vs rhpFy}WqV sƆWbq{jJ揊ِi Y4I\n昀&@y V`oYsaP XvxwDwe&x&6rWyX: IxgSf^w7zE]&Ed(~>АSj:@^ IUHi)|Ŗ8xs uyi^y4C7[]xW4)Q  q boǘPרnnGd |7o ?ɕGi\ƈEMX5GQihicpFgV4GH`re} si _ I^ wg5_痀9]X扞ɞ yZpU#y3p pPy8r6eyVd&1a`׀DҠe2jI`p'i) fp[Ew)  pF Ё{5nF||htY5i&z@ilIg8E8}cZ ehNz]moZ*In'}e఍ !d(9L P`p`K$xd;<@Ze%8Ai5bY!*i8LpQDvp[ a~%  ]Eqz-h7 x@ ԨazP35P_! ,! ,! ,! ,! ,! ,! ,! ,! ,! iH*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,!  H*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,; 8 *\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ%A0]ʜI͛8sɳϟ@eDѣH*]ʴӧO&JիXjʵkA4Kٳh&V۷pʝKw+ۺx˷߉w LH#^̸ǐ!*L˘N̹ϠsnMӨ#Nͺ׏WÞMoͻ7ܾ N%ȓ+_.8Уw.먩cν{dË<ӛ5QŸOϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k9ՂL<#S\SM5dB3Б(#N5]#8LRnqIQ!R<&)Ь)w&AKd \7czG0MttV9 Ej`kn ?~@ !BM\rq. jbz<5 75`0 QLH.0<ixFka ۗyC EqӛAE-R^|AacGVICAqEcIQT.~Q [H ˨3FRllҎx$A)bX5t!$Bd0Qc&xXU3p @j`tHB8aGaK؄%J6Za+C%g7(98[d6= ~P %M7@n6yGMdKaud!CO{ y67e28@es80M d` zg cG7@[N6^0&W}SQ18}a:\7~%S~P }(WXhUTPiVlHx|jŎ8V)~hH 8n(?ఇ(3m0CY$Yy oDx C_T@ _S ܐI4` e0)44sxx:ȃЅ [+ْos3HIXtt'gc1 Q =pDtD90F)KWɔ3t<ɄQ 1,JgaiY[;=Y_tgn PO@ >LR1w `HQx{ x(eY7yI9Sn e(` zPCP8(h A { >Ps|F:{$2a:y&TzНW)U5Fyŏ ؝4VYs InpfnM48H &yn1 765 p* :ɖ|>=(20#S9 Myrm ʢXF^9BiJH5Q8J.r~qBڢEZ@8i!O?)n/8@ܰ+3eZ8 Mz׈TvQ IG(E% yњHjXqga0g8 :I{v{FM(}f9`Se|0E|nrAٹ sR|FP 26 9Ajj ʫk)nyɥnp6BL'2I5 2dCEa x1XoC"oӮD:Q:M\2tc qs9G_(>ѰWڗ?٤- q ۲ KoM f [?됚এa0o\5 P1:+S> EOz1ziqFQ@`kRS 7sR rGEY K~eVFO3 U6Sj-: ]ء=ڠٟ8yJV<1!}L@"1][z *q^s.*uw*y.Qp9^%0+ q{}ա#=!=.+60 *( ,>%'N>2ppp  " c!I AW'pn>A־.#q P$~# ? a:1 a'5P Q+0Q"Q!1ێCMؽ^aA 9> Z?D4Uo!npro{}O'%@ ~@.Qa%2QkS"p)`^<6zG6#pU9"1n67 ![sɿ1#s4o@Я>C?6͎!`n* AC  NEʕ8fC:W֭][lgG#  ~PƆ*蘷 ." 1bC  2p/6'^,$l`?ؓ΢!;J #p-L!,n!,""#L# PBN#:G*%\:AZi)LjHh?Vd-$ڀ0jI+K,k~Ҡ?qZa"?EB7?uLL4 dH\0ZGC+CǜdM5YiV[oŵR] 7:Ь7_+t}":8T;"l70h$~Y3܌+LRZ>u#L{YPo5^Ms̔0t0؆* .hZ{U#wj̨#eb b1-ʟU\ފp^/L]]TN_ʖg3\Su,S5b$Ƌx'8ոS됽G~v5Cv(MFɗ/-4 Y0g=A/\̀W Mº~EJ bRpUr&!^0Uh% L$HA Hz Rvla)KFp7ao”@h̀ZEw!ʥkMk; xdtpJX(`]!1=]qzHcT<~==&EA1MHRf8FS#&zg>@"A4r@aDw \cckRy5~))t*[G%z"kط@ #$!3{W.#Xb>L`PB`H*a*5:Dg:.-Mb x e hp={f&EgEޙ[lWx>uL,yhTHbN<-Pk>v ae)# 39v2H2uUQn$JؐR; .ؙ$#(k ݫVdSRUSfw/)]>58بrM}?"niK*ؘ3psN$kp8)A}@6/dP LZ2N #: AYN H &Fc`ZUan9?P/źs̟<q6 Soz){:?qԥFzx24;f =˔hNj0ڤjSFps]5jLv$'5z&pR*w8s)fPw 0p,~Bh\ .ntN׍P Ps:ci!D ahWQN.};P7x|#/>O|c` ʺt<׌ 8p1 G r1i#{~#:3) `vB'э<|WO m/{Cp_}3:J v[ަAZ(O49Y{ֽmikذ^pOg~N?{dUJ3K`"=ڪ*9u3aa}׾Wp}_h5K8 釿y1&l ?m?#CS@[I @{  $DTd >tAmS !$"4#D$d&t'4%)*+,"./-1$243DC5d6L489C4t;<:>?A$BT@DDTEBCtGZaduIJDLDOPt 5u(S5fSldEW,fhSDrk+rXW_ ,E)r^4f`$eChF`)mFGTlD5hlFU\eEs$F8du|Gc%LG58*lG:GJ\$ƀH7$p\,HG58lltrhhAlH\kHpȅApp5(t0EeTlSȌAAljhl rI5@sEAoS]TIqɏDJʓFJflroX$KT˸|„dFG\FJ@HdopFĆuPŴqLLK}GlƬɻUE¬5pLŴdHGl0r0LlTGoHFĬDqLtMdM{M4GL5KryfDTo LޤK|E\tFglJ䆒DΝJ$NAJhOgldȽ pLSL58Od\O JgGlTM9xOJhLFlPJxžl,O4 CrLK}ZD<lxO|!QNL5 ["QȴjjѡQLUrJ-V ,؃ yL }AeRjmIJ"5y%Rnx|ٛ-9СVk}WH$^MhKXeW٨G֦}ZeLZȞUmEuPڽVZqDM4LŜZ5DžAl_kuJ[Ή[mLSWJe\N_NSĆk֠-ZpbJEĆFLo8Ep$ʦL]LҥӍNVeLe>mjfLdqh&g:2eduNehfV2jX_pDF QX4`.!!XC Df@px]dpj(t@nȃ{\7Sv84Fvi<oЄkN^fx8uojnZ?NmrwXvq'Vgv@.> vVbvfrufVrPzc>hz{u"hgql7m<p0Fd8~ g0fIXg^cvxwxF"pxsnrԦ/g'yGph0psw^0hbV'UWg]O{w G^fweIw7~gƆNYGe[}g?gz_Owc7vg[{9E(A4;XP!C >V Ìbu=:E\R[P`@,X[JXQr=S(5etn7Tl$ CQ,GY 5zALBJs d| ݒ(ɸfUQ$5MMBHVÄSP:S?BPRjXqP`6bjmp^rXϊ$mcnKJxG%$򻧿RхR<C8#uu(m89L"T17@˰ |*9O.-s4+8ވ2CnMRĬ2E/s.ܜq3P#'N왮ymr/pe '[&U&Enˆ}x]0ݷN8f/V ,޾;¯3U ?<3\<_&_9y=oo||j:\r :w)pxkD16)Lk Yp?@,! Sp.|! c(Ұ6!s>!(!F<"%2N|"()RV"-r^"(1f<#Ө5х'(9ұv˗;~&<$"H \x&0 "3M{ Q0Y$'SU,HfHFpcHeC&i9V,߰dÙ2|#mąj= I˔k2+;1)M̚9B &ݼ)O]ϖt8ǩ}ѝr;mS2ᙨ&<)щ2dhBсɟue:u[eF)ҕѣm]wgQ u (je@=@gH35&P-Y!wVv](XH&L6QjH_*" @0$A)0 !`?sΡ* ѐB;e 0lVDqBjh`O&@ piv`1&-A1o`A, $p@wG/$l(J:e0,!o58<@-DmH'L7PG-TWmXg\w`-dmhH5rp}6 7XaK,a߀wwߩ?}_PGvvÑ9,XAݵܹ9'nGhgDF  Pr5' Np˰"4˞Ǭz4l WYh 8Oޫ[wW#W4t㨮r7oqݱЁX?]Rm~Q;9hP rFaVX w+RA DT#7Bp~;w(" v+|E@KT H:t yH*G'` .pi5W7*,p J;+Br:\) @L F`pȏL"F:򑐌$'IJZ̤&7Nz (GIR򔨜RV>,gIZj$^BڨF(򗒤ݳl7G)zL$Ss{ T4hN4MAP&4n A,]CdIGiT4=N*@3*V7A(@*%/x AdPY5GkֵNU`&- @lLẝX*E%r FUkCر[].K8 ) ֗:x_8p.1ywOo\7vW)̗8K*{ GN(OWc8,R50Т 0s_|vU P"D M;3A#>TT)P <D BD~u89 ~t[%ܹ&K̏)L"8}*A@<#O~1@8#E/|K/=cGaA}7}pw~ģ*X 92 tw{c3 K]P/LaXMWqe.3{[  Ȅ@~/8:+ ?~T Z0u@ Jfa}}xVQxСB1;ʱO9pnaa!cYb !cb6Fb9zh&4!2U3$)6,7:C"$1.a(B  }x0w1exQ60c@Ȇ`hpQx}]0h 80<: Q`(^P0v+P! BRQf;+(`eXhkN[ftUdO6@WuwMHE53=s4glyQ7'H8(fp;`u I`(X7ݨxMO>CQ/}+A%(A!$g$wiR@jEVA>j-m6"vz2.+Ų22#t#cxnX ?)!1 APaL*) @H %;HOلiփf`)|)+-b3h hmr3#~&tx%W?y5xhB|WXD(A wI \SљI !i~H~9Uu9S)4A=bRM+–L$ tAjHI@*{Y!O~eAsX@1qXLؘ>1)04 3yj{g [9RPyEَ&џQ|0țWy`v=LkɅk)a6+(B4C~;ѢŃ[:Xk2@,/Y,=F=Tgɓ5P@YJ Zz0W``yl*nz яqQ`aDS/2btsan"n !6mV7"obm*Z5@4Zaض6c:H..r,Tx!tDnԂ˦   bJ*c :Pn0`C vYЊ|5Z* V@zW(:ꭿzP::5FS"+E PaappmrK  D 0cp5$EE3{EU/Ғ/b`/#=Y0ruuPM9c}J˴`uuXa5}rH {xe[G Pu_ʵ^;Dkk"H3Ѹ18L@)w31@ .2.5 ۹;[{ I4w2Wm X1 ]t7E+a< #̛X!4пv\~ zPť ѠEp7!q?g"!g6~q?>p[e2+bO p=m"\@\a#A,oQ.?D_F JKPL/EMoJ[^[\ qqa2c7hO;\wlB_)kmqr!F'Gf-¥-#^lTȺoLb=| h>OMC4e<4NϨ;C[S=/mPo"5MA? HP&4o!È-FxC3jRH%MDRJ-]SL5m޴MΝ!zPhß?R̘G>tP>*)Q\/njuS?<[#٦8ݾW\uś$Ob1>:_e;V:V4RI'RK/4SM7SO?5TQG%TSOE5UUWeUW_5VYgV[o5W]mv6Xa bMA*h.*(OzEGx[oim wrUtڒ0qxpow Jp حc( 8 a}+`ٽhnc3 E6d*@0g8$ @d6a zcN xdd)"; ‚5jم [(l;_ (N@ (8EJ `Xn曠287J烦m7| z9fW(Y_l~W:;[a~Gd y#rs{D!, _gz'?GV(0fW٠h('j xO> "ZXa`v`[]b?O^`AAB<~J# ቤD  !d Fk 1І8ԡzX>&6QA,Ҁhm,]Bڥ9C!`-_ϘF2t Zq"@ؾ Ƌ$6X&d= FHAɊ`YYHJNe(E>Zt9v%TZ+  gcA$ `e/E'Uʑ"a )AN2UR1InP {'8)Ë`xS7qQgj`i)V1wW1]yy" H?h ,"5(0I7;곎[f+XўEy+E @}8"& /ow5]-{(@Y=r@a(7Ar5x"0 _ JBr-> ˙qE'FLJb/qeHD$Y@*y=f,AET#46*<$c8Lڒ}b8v8t\ęHAif`is 5d0 4>鐬kZ-.,h`99 3/WӳFԾD5uVH7&KxS;AB8c+ @]S% M1$h *9RʀFN8| f _o|X@0j 8 PC^euB |pqj- 0<z|Ti ȷ%HD3z󁼛 -rK&K`:ސWo]KL>S?|zz wU0x xѨr*;e&GD;3)6mR@Վ}C ȝ<,Ś/}wzkj!Ru]E Io -Wc=¾w @\1HEV#N78X1 2+dh t 4+&`&lHHf TA T4 !$"4#D$Tu%t+'PiA棎-\ 7/PBM=41$M?90B.C@8=,=sJ؄NX:=>D0 >c @J0)NDHT/T,7dpDE\[DD(D㡁"8MpH4P:n+B]F5kt3`e"J@FMB:C:@ \GmnǛEZ ϋr( G!PG؁NC(Mh zGӛKǐ:9=*EG؄pɈxE\ȜI&ɟ M{$Hx,(P8er܄ :kID9I0I$QTNDpGaTyݘ5ʢJǮ˿NG(9 = FtĨ#Xsʁ܌-A ӌ?s #0,8"PNM?"Cĩ<wDB8vI[E~D$.t`3d qC+O4ϠCnι(M1fe8hNuQyCx =4ɿlŎ NH@Eՠ2HMP؀ @H @ѻȅGtp5 0<'mQ櫌+UR!/ 71=ˎLO0UP0Q%R\:%gQ7E ?= eB@5TH[SxCuT(GIJKLMNOPF%#AQH)k8TARe.# Y]/s\d) 5+ _e8 ULXTcɈSkBaHۘnmoVP31!syI;hAǙVliyq YAX_j*i&hЪ3:}-A"a(!) b =7{0p ě(C9AJ!S95A~B?b+0؈xyXe*=:9$&bEZ Tڹi"Z}"`;[ۤ{((`ThVس ܂p{[Z`k;ڏ܀zYٌZ\-%[W-@Y Zrܸ) "ȣKsݬ].f<𷙺*ԍ#g%]4ݟ^@=m\+0˟[`[=BޣJނ 0-0⭾.(Jm쫮ʮӈ/jfjzWUۊًUM=U͋  lU.4 @ `StA< `pҸ9:e '+Tb?y,b=ŐZ e/1+ 332/4>h<)v7HTh@H :եhbʑ*D@0;D]T:č#>>Nb66MSxcL4NO4HtKŀξlCNH&eJfүe+ZFS0F8ec66HVLЂ@0T@%)c~Lh00e6fg>gTA8)efgTpgifal΄HsffuFq )b>fAf~zf}fdJ;xȄefgpgr6geh>)rvnihfj.gmgND CE)T޻_jF'i\X`#_f9e?i6ӀfPd0^ͩB˞F7=*0@8>AЁLA8'h"d @onZH$0kn8`ک8fQv&LyShf~ȞlЦ&(m@Hgdml&&$jnKꋴ=>e@6dsj[9@95k|7 'j pހ~Xos*vڡ^.cSHPFnn)Fh삀q3@$ '(8pq8ǀ778@q"r>f)'(LFH`` r!,`Vsrr+G̮r2#g }p@~t嬌JȇH?/5q T/RM 4e[?! H@Hh`) rFZb/c)008nvyerp~v2B3B^lGvsoqXPgq`sf_'hvbv'4xYtrrlGHp+>?G|ГDVd  R7ӓ]y^Na@Bf&e8Nf㜈Tz=FHHzZ䑖vjkzrG<SoG?o?%:`⥃Əa^zkVw{":xxFh`|P'g̗G{g|?NqqK&yDmLYyӌp_yGpE_IVe2e.S?LBh3-8Fz+wvwh>@41iF'!%,b0_F%hQ-5JG3|fV蒆N+kt0)18*Ҳ'ȟD6b̅5*ؐզ/Ob]ϋ/Ki]obd{үI>杫֮Uu1’ NW`ĆLקCV齟Ew&=1侯I v| gf7KҸy-#tbPONQM5Fa$p;ۓsd~4D@xf+` Rφ?=V e HoBB#s r4TTPNy5߃}(x.ds@kŵg]WphllDx#ȟkE65%8eU^Z`RJ٥f#vP6 a)I l#J ! B70RcsahEi?|pt Ґ@g Z?p0C OR@V''@- A|0 lIv& JhEހ֢D&6 A>QTңn8) b ܮۮZtYjEZ7V̩Zpl%i=nIm&qK0NV{poe#p E[l^\fb]rVd%%v b e *Ta )ZQʩDK'Ҩmt@QM%4#hmo|P p@'80?2\pw @>Ed6J3M O-u0H+tqHYfUWtu:h3liЉv?rml!=Z'oo&KLr<;wJ>ow,FM"O>ԅ)Lc ȑ9I, tzk(YS&k_IZ|$-h)14nO=-0\ΙҮuܽoӅlZgZ)'_>K0ڌtLz֝ w)Y6v5I}&wx~2g2n!Sm4MU͛}??pуOճ=c/Ӿ=s=/?>/Sֿ>o?>/=ӯ??k?Lx(ǿ?` 7| -%k@`A$/Lv1 }@p݀  T@  T9^ʴZp F _$C>!^rYʜ \@ 8\@`l @pa D@ adK`@[!ZҡK!!X@ "@!@na> `)~bM(Sʔ ~t@XP@   &v@6$֣ac%##KT)"Ni4 $ @Xl5[x1nbPc5 ڠN% EK%T9zJdXZdL$ T%[[&jK[4hZi_v&Sfd3dYFj#k &\@D} `EQNRzfS%G]$eh%:mn~'3&bZb1"->gk#pFCފE K'@D&%`L@1b3'@vu>`7H `wz xF(2e#v _g(CbZDM[ha ! @$ .!6%  @}aF"fh.dlB.0AtޓzS`W&1@y) ~ߔ0'bin)@@ ѩ)ũ&&*-*6**’N*_^*fn*v~**ޟ(p#\C5T1 15EHя(9C558$0PA.!6 'MBx=&,E^I')hXSh+8-m'Q+$]+Sȓt+~k-EqG#P5TNA5,MQkNRRbD.3Al30/`+ +SA=8R6@bD֏**(D'݀tAHE,8bD58\-8BlA2l0A5 ?&*$-ªnZ-ڪ->RR$)0v,F| ,E?4$.>lm؀ ,RLݭ,@lB'z"́z%%Ԁ\N7?4hR-* j5L.4PC.5\3A,+5frABLDN/|LBR(Z$D@VDNo.6//fop.0K0ov D0ƫ1-:P$:7T?A2C;1Wg1/L5?b0A;:C4lp;-bxnA o>l9A1x8x{,\-7A<.p8/A.|l?bC?r+,\,Fd?A!&C(*TP(B'8VA|&"A!r%|D!-(89?*D(΂\LP*X36k3ts03:ή?A ;<3(3FL5h7P7A &Fk4I.<,4/eP)@ hBH&/׆?<-L[/OOp 5 A?qE-/L4MMNDP{&uO VuEpuR[0DXDP{u>pTtM1|-ѽZKMMSSN-LWtOk0/YYopacD_K5`5tV5ba3?c5ZZSDõ74C5<APC;)lmn113pC;\k/]7﬋øn?8x??9&yE?tA!_BxD5om!*5KҀ(#[:\Xz7T5pëA+41z+o(z]E4: <a( ˅ğ':__\S:E0:WopDz4D:NsG:E:j$)[A;h?9OD ;p?qS)A;b_7lthw/WR,9h11xnB-l6ӷ0,n,r/rm+3,ȿA?O-2D%T†D4t! `C3dlD,A*LDdC}9C*@AB;s?CS}-_} ={׳_9ٛ=ڟX9E/窉WC.pkND"?X.?D~>X1 p;u_hzuAͅ;ӀX>D@5Tw퇾P<014z?u 3E35dSC_?uAtD"h(&3Vw.@vv54 ݡ 2iw*pMGQK\ĉnjH_'q03%\7& '",\-St'af5Lԋ2FQ5EرDa/seU*]T jU^ZNg W@y9,ʏ(5c 愊@#UEBp5A%cl͝?',$:ҙ V5lOizy NݺKXځ{ꕉy !bFJbԪU&Me'P@rᏠ<8JC!Fhp* O?i(0t&I)L)C6PR0o$ 0A|pb$BD\@%O|r(*ϛvoډ3NbnDG-;lGςJh ,J#lg_~IԥIc$7|ӋJ(b&Z)Tk*ar ,+묔J@ .VIVU|+,8Lia k3*Y7wPf\s5[U-0V7sEndU\g;u.بC rz8% ȹ n~ѽ hhILk%.Z~y,3B~C9!1CQL=<_܈o=hjM&ICPVYA\F46ur닺F너h쯑Yi!olph΋pupouP"QN^OzҨr!䜠P $BjFjիW/ ßilģڵX_""jȰ: "M*J ml;}/mE.!7C9zmO{/J+!3E"v #y8a$QCƒN8ãp>jPB()L UQrQbT+gkMP#?d(7[;!Q?~qGCbJ7m/l" mJUJFB ьt\agĚJ^0v&c8L"wOq!kǡ*ҟEh'#R aېNV\`**88HĢjt; o!Ut3n[^]<$ȱ.R-=$ XP(*ʬ"z™UH!-#_xY?ْ$3ʏ~?Iy"'g=O  KgV$Ո 4,r 55HeF4\h}ȴ&2GLF4HNKScHaIBo$.3!:-HOS#E=jRxCMPU+,-I=aCU \mU Ot*AJe{,VS L2W",`'p<@G.NmaPi2Iq%$,c|LqUz;:]$1#$KGbab ?.,a4Ƿ .qͲ6qtBj 1p06pdnP"G V9\_&@9&0'N/d2hSЃX$61UbƁlh|* ?8+AX8ZӰR0@WUI<\l"?πR1֜j^a8u~\]юTL4 Sn3:(9y@o-Gk{! kH{z֑5-6I(Eqvx%v¶+"@~ZG% uɝlĕdp+o`08 x258H-H!l<0J{g$rN69 E|N|:C'@ \d"u5Ȃ*M31!|P<=Aʝnc&3 _6(̎/SԥWYWE0Z /txbt;8NjC&)LYkz!DT8USX6ycdQ^E;8 xcNFo1#YǃS@3m𠜷w x'+d~COC2 Hrgfc 1 E 1K x_؂(yD^?N-˿DA&Aܢ0% c!oAPMXH.:=Jql@nr0bO0 p 0 p 0 p 0 p ɰ 0 p ٰ 0p0p1qPHI!&a!1%q)%~5q909o!o<Q1UQAb@Ƣ'b&bB#&am$tP$Pd-*^1S$qZxzXhlƏe% @j$U1 !q.~AA d"Qn e!1%%p rAMNaN&P 2*zT%vlCAoMZ#!$ Q.2# #N##Nv#`1'ur' aUc &"J$SBeD ר-DVe&3C$ntrU2,R겺.o#{ )1 0's0 xP6-N.נ4+#7 NA+tbP,3m"2&!STB4U ׁ4 0 s7yjgjsB#ZG*Ԅ2 -0eP)3X1/p2! :<3urfn3%&7s>rQȡ̆*S}T82&2f[s+19" `Hd",%3?S<#tB=TBAKړ&+sEYtS7/bH?97-)< r2=:nuNt;S; FSBHLԆTT6+ESEJɁ0q#!P r)!KGs6Ft29(T4UKfN? PTBvjBtQ}͊` &dr 1[⹢PQ*31BTI@cHD.(O;z ҲxWORRRHVVcuVwDVRv48"45[ b<༁ #!"s,$P;%!f $p6s5!&&y6NMb)oN[sVaac D"nu]/BRo[]ep`Jfq6guVP3p,Ƞ1"pkvgviᒶivj! ,! ,! ,! ,! , Hjծ" hHHp"DQȉDCIɓ(S\rEyV#$H f˟@ JϳA_B|# >իX}Ȥxq騴bNV8ezVu7ܾzNs "ߐC/6 (jծS/D(s6ugU޺{Jќ^-{&\ &mx?}!AYImiB ޤdž"@]UDEjgu%/iZui(څ "@x|1!;j0*~L]?EtDDwHU!YPg@*Z餐Jꨇޘ馄t~OO5f4 YAE#Y9i'P%pZEy kQVeXkY<|Gl(v4ZFH4(NY{@.s8KJV¯pF0  Tp+ "J&J$M?L,ν23P$ѳV4u}^ YភDEm Z9}E Ox~xe}Shp *,n*z$r`SfMalۚfp"7D@E f&PMٳ?$|rtH!uU"XWP|bH?!sםay8?k(<v<̇LEt Xys}uLP zQ&΢'vk;m Dl`JBIk0D$CG$<ݮ0&X ~D`>P"h~CU  h`~8gVLr|&PK|=PT^#\ OU !D0ZE)4z^@@2 V;p0X?.LxDyDce2G<Ȓ-4OIj;DBOyMS 2J`y@rLwS'y<Ђ$2}! s<愐қ17U,c!7ilUtæ6MEDAw}EXsJ)Jy?#r"EB%|^{@ dD8s00'5*a"FY ^MvݛK' }BR)X ^L1Mmʫlq& aoP,ClS%&^Հ銈H8 * [M32\*Ġ5$KTh CQFIIM\'K١b?lfS7YӬhG+8.%! ,! ,t&NI5*\Ç #JHŋ30cnj C r# I\c8p #Ǎę2%dX`-I# Af@NZ p#MJW5 Kӿzuړ]U1&C"j"DjV$j-F1M5)}I8YL Tʅ]kDr=2 PnXikq/5Z.nu!r4@IkX<~CO~:\~!L&АoDHQG9aǐUWr .GDL\8w g^wWwل*8y8Xi#耨o aa݋]C5! !{5$P6B I>ҧ{Z dm9$C#-.W5UP rene8Ny XMѠE/ܥO 9ET{FGiҩRE$ BMH믨S*P@! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! iH*\ȰÇ#JH`@! ,! ,!  H*\ȰÇ#JH`@! ,0  *\ȰÇ#JHŋ3jȱǏ CIɓ(S\$—-cʜI͛8sɳϟ7_"JѣH*]ʴӧJիXjժԩ]ÊKٳh~۷pʝu-ݻx˷/D~ La+^̸ CLb%c̹45Mӑ^ͺǢ_˞Mضs( ~7ȓ+oh|s7Nuӯk}qË}軖OϾ˟O~lϿ߀Q :~ F(a7Wa6v!z~(ۅH(.gb,ۊ.(k0h㍣ՈyhI$a 谢CS #z B;Gh $ IBL"F&8#QĝTrdN4鑄`2/%ʨDD*!'RJ@2Te+7/a.YK]UIH,Zb2K7L+Ġ<`f2i+Ԥ1L fӜἥ0iM`,)N3*ld=sӞd ʖ4}gDڻt(aCWi:s}XOTԶm*/UK[ζ[ n?ծt-om>' ! ,! ,! , @ 8)\ȰÇ#JHŋ3jȱǏ CIɓ(M\eʗ0cʜI͛8sp%y JѣH* PKJJիX#6j֯`ÊK쿭ٺ0˶۷pnDVݻx]J޿ ܱSˆ+^|740ɐ/˘3kवӨO:nװc~-R֤g{c0NxcHY6μـ ؍2afC=hZx~(l%4VW 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7!033C@=3O ` L<26`8k:x#7x8ƒh735 W@F5+~:sxE)3-^! ,! ,! ,! ,! ,3 H*\ȰÇ#JH`|QǏ CIɓ(S\ɲ˗0cʜI9X*@ JѣH*]ʴ)# lEOu%!g"]J wYUw1\=ʐ+˷߿ \T2L4 )&d vMZ8K?CR| !̺װc˞MaTw 42Z'_4h\#pͱȾ˽L _kνq ma莬c:;b1,5~}q-h&!uTx&2Rx 2cA=?xф"sX,0@ }gaȤ}CH&L6O oБu֎a/q\Nlo$O4,a"&ـbXc&袌F*mO"ǧARob*jꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L WP6Ծ QרF5c 3p!G5u qܐ )R ӄ y"_&hb)!F†:&Ç#wH.ܰ=$8n~1 ьh,37vKimjPslH)Ɇ Mu2(d&RG.Dq< 3 DC=1: > iXb#=bxVq@H~ y"An0A$,!pCX{S =:R&LF>q"*eԜ(?N2( ]hCR!E!t,DeJI<x).b~ClfAYán(E0MtBV9l M1Dj`kS ?~1@ SU XNBj 0?zִ. 4 1׸3ܐ)’Ԁőh`X*V H@p{%3чP1x+_ XQ cĊ)1e1K rV Z|Նv} j0RCmԭg \v=j*]BVe3 hQ[Z袶kֶ%rBv;1dc(@ q ( |j5BЎuEGxhxB5# "ҁ!X% \j o#, Fܰ/Y TQa(56 ӂvB!¹ =ĥ5WQ )*VԁY /tdkf7"u&@dPs%0" hHK7Xl⚠à0mYJth63ҏQiYO:ӛHil.T%2 jhj@ M ) B0A7bm]q @&K0\CErY5GEy n#%d@P`dG4#+ (I{' 11w ` p:5x6l0fͱ v&P wTi 6@hg MY Y`gGjV s7Y`Q Ȁs PАyV3 Ipik"IP`E6&e0CZ u)5 hIД pQ fg@LPl&E'!(jaM&k,K$j>^3iۼ  ɏ~\KLiج1 |`CDk>EqЄaMPeE0bl4 Xp pap)y[e@ь}sZ"M$UstG2mF`{>^z:I. =Wӆn!=%=ŘnlO]F7U ";@Q_a! ހp mvz֛@Yƺa`Ҿ@ JyNnI*+gL/S QS7+|~ xf7DV٧/Z ]}K-|W}80jfoݟY<) 1Qݛpݫ۽|h}M<+ H 1' >Kn# MV芧AϠCֹCƦ `e :TV&)-ME9հ>a!N^) <=y%=U\7ney;Q1GF<>v>,θuP>R$^~E~=Knn]ԣ0dKа V Evx>`aȇ ]J~'9&b4AH,ƻi{ ]0BfO{-4L LtLۛbbf-EEl fs 1dVgSY h@k|`hNNif}꽲L~MϾ nNab8YOUaHCI<ϙ P OTp_ONT`)Q L񁑋 ! g `DiDz NFllV<51zAA1cf?xpD@HqP0H J ̐b<aD7N ;azI T! VE j}&+tcN۱.ʈrQ/ ɰ% *6>CAB?_C 0 6Noo!q"IG @0ap/ &/˯N. pa"j/ZIt+p,A_AC? D@ >Q!Á-^aF=vhbȇE6(IG&\I0&Ǚ4IRgƑ9wʗ;c:gТ(bЇiZOjPX7, lZ)gVD 3iж.-$¸˴`- &ֆ}=+eB?BX 6* 48F2 ky:s8j|i>j4WCp0$O>n9xk{m x|mE|y7$cr'?[ϡ*(Ar+di -)d*6d. [p@K>BH ` DPO:R(>@H:8 $drc a*JhKLSOJt1:2O?p@k)e4|C4t?T9O ;/TѦ@s\]/Io\'t<͂A0QC mP T hʬ!dj12 p`rLwQO@Q:5OW66a@cK?MtST|QU@e,ūv^5cX⌙ݸ[3,'WS?*t9I V! BZ8Ǥ>*x qc 麌۪8X 9Ӥc&8/ִloOW\m6ڱԼ}1rw4[1UjtңsWTgđ>̟F i^ћVd:ȫ|DI 8&$@#iО{r<֗nDﲔ e^PտPqX&tHlE#٫?mI b ]>G f]^AYD$/, S:ֵ(\D*9.*Dkj$2THۃզ]"%x) ]+7ƗEІ;dCg펇JsO =8 ̏## g= /";n1V!0īYE֖,ً)#hbCj@P{)H $@PI s$Ui^VڶFRRcN?@fL2n.ܘO(33C$.GgqpW!BrgL7e(fnq,Jܡ7 ϛe4OK5 j@k& ,- с&hIM2 C%ҁ rK@ֈL^) pʳ6ZHi,d@C9I٬'UBYvȡPp|$>:u֘"wv$ 尚U;nuVx(ege&MWI<&j2fptAiP\z8F\z Ѐ:*jI@`A|]\8f1*|>闿z[϶VGqv]S)XW6wUڼjtiVvtBAYkjI%)(vUEw}YWȽ"n[3 m;nRД-݉KeiŚF5`cLvu! 간+b&K82^ i_lDx70%ݿ~2:/!cӾ@F2x20$Gɕ2lLe._3Uf2gFsռf6ost=hB75 C7яvt%=iJWҗtGwqҟo2=jRԧF57Pѩue=kZZ"umk^׿s i`Lx(bfˆ6G=CGړ7@k=Pz^:Vt$Mv 00 -`Q4d(G$R3!8!S$xG~1qI308H_^<<(8U_!2{qJ&ttBR򺤡~wq@LpH pRlpR$I.j<#-"bq88kw_"CGD}'{3ƔG%dE 9;5 dzCh̽:0Ż~CA NQ# {AP$޿( "}_"7)ю3(4ƶ !?,@9֡qb#>?1?SȻjo<.|n( <`@pxת, l n(A7tX"_08e;snظ_ȅ\joA{QA$$A|@B#[?˾>@|%lBl!2X=֫"0[0`; ȃkx8Ёknk{CUTPE+"  k7bx67@s?P@`F?>;+l+ {WTZ4\E^$dejx6"1bx6 Ttn oX<_ XFrsDTGvw<@!8v+ =Q`-IAx7,pzhx6@ gx6&cLs‡ȉ$ 1}GTHGq_1ƄiFf(IH0H ˄@̃ &PE@+MIT,$x+LG<89v \AHLʴgX kS`IpɈ`pLDބ$@MTM6MNȅJhFj4IH=HۺH߰ȆC8rb,l<8kI?c<4T̃*Lђ8OVDJҴQp> CȂu00CQ E!R)M*҂崹\-o8&E9-k\ r(25%6m?!/5+Rp>o0R BI<7S;Mw!kG>4 x=U=߃KP̬OUE$mQE%MvP͇X[,FDbcOHEQUV`VqpV@.Rv Rr$78dMW?]Hs-9tUӂTx Xp >}YoK T|u@}c-Wׂ(sMWbcEE>UI%JXh}V ؁-XJPePIp@gDG#TPGVp Q7.Z#MkYV%&pxMD?T,~CYuڂ|tȈuxׄ0 NRTAeX[҇(ªZUYUULB}0C5ۋ[%H YUh\L=,7LCtYc;.)p>P'Y Q@ܬC_(hYU^՝PZJURD͇PO*\=EjElϮޥ=㥺@#Ex-cKn n!քPZ7и4ASPA|GoB{!t$hFgqLungx/t@hDp`~@>++(\p9 ' a6x@*4@sɫJ * d[ `pP`=[@#!x8)P?R`?,\VF$d ҁү6 |6gA}+H@D+` Fy%D & o8pr GX]-cw ̮x.U)CjqζD `2ȥ./JXR!\##NӠ@LfZ#$(/"X0J "b .rA$G;K>S!+0 ypDjZġp@NˇW7rF ' :) 0WBѳTa^L1# pjS}#H"X!l 4X~O-w u ܋B+$DoAh20Xy.t"]}ynzRx*wKyv5s|a(Eu{W-L\ 9oz' 8ip%[70<5x^X&I8 T,$q0}b^Gdlb '))A2uKA2[  l?3VrSLEHh4.cb'|q] .55tqws^5sn6 gEڔUbH#6Ԋ0 ! ,! ,! ,i80 *DP!AhP@-bfƎ5b$8pܨ8X.$H@Q31hWfN;{ ɒ"u=|H[~2 H 769hD-m֤iAc7+hNq3J wH! ,yi80 *DP!AhP@-bfF9r$FƼ6_x .Ǘj<%&E"."ЁmXR`dK#64j©UI Ɠ_5:aوg! ,! ,! ,! ,! ,! ,! ,! , 8*\ȰÇ#JHŋ'ȑ#CIɓ(;H˗0cʜOeGiɳLqfIѣn:ӧP*eիg LJكZBHٷpi (Mݫ3mH3˷a~ |ǐ+dH0c%D9Կ٘\ٹ隟FCk~g{gۇ胛cέ+pPM+Tq>n5:KNm{Q 3! ,3 H*\ȰÇ#JHa1VȱǏ CIɓ(S\ɲ˗0cHS͛8sɳϟ@ :FH*]ʴӧPJhԫXjʵׯ0^KٳhӪ]Rl6pʝK]n˷߿Q#'ܩ4Wü -L˘3#Q @!81C%k^ͺ\9ϥ?'ǣ:t dH $YanN e8ovν(e  bܸt E 7zʛdA H/ոG8ahނ 6`N;܆? U|` 1(2p"(4g@P閘z<q1b;рbb/5F)Ty<# GF5ℹΐAqdKnSp)gPW 5>V8 $$N'@$3碌6(Iar?5 5d7"7af(>jꩨZl&[ C0!8n$f::(Q댊h&T γ`a ĠΎJ5yxHq`),ސ/Lw?2@'ddX d#v7Ú Wgda ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:J H΀#~$+ȇIH>FiS2JZȒ$4/$(72@" &UTŕH*(ˆԲ``p '4DȃSQAZH>P{@iBqYyb $"l~ f8"Iv';7 sFTg!nph\4I< :Np^= 'IO{]UB1NʓiGT4,% $'@;Op8 (: 1(hOSB(?id` ,p|WHVK8T.O,K` 5  dC.:+6XHb۹*6.%KYtAR-iMGrB^Ynd(]EYkSsҳM1[Φ֬5]lnB2&dMdžlK`@ A^; P-(3QP+` U~aG2p2(-~w_WM?(V\?`yѫ^|7Z]A&ÕmrK[*}lbļ튅+lQUƲUMZ&y+-Hox81o,Ť5?;f:G!ݗB z * dwEzL`86G߁ ;` @n qPmn3ׇu*\rZz㣳]:w}6:ȿ[Gnla1}{;޻>ٯk{gcAxOJ|J06w|y yPx%!(!jrf"1vn[Ps|8|fsgutnQgs;7o&l E%o1vSt,{tׂn}fu[ff*Xe}E]fR[|pԀ5x !ǁW&g@RifHim`'r Hr(!&\jZ(HwVxŃk3(恄HphxcXէ`3lFX{vtX@hQW<\fV8~3d,XKL|eh{!XmdZpu2.nE!*XH $`ep GaXZ@xa-@YWqĤde G 8vaa}Udp)jGM䉯thlc얏2FfJց}6}eu*h$z82Xw4m&W~ ~6~~Bn:~M!)gƑ)TkoBۅwSWW^Ȍe GzPgpT>wMiu`P MH$pCGUzP6;Gds#E{7|EkVi/zZ{+~WVR,C׈LXs$|vcrD8w'9uɗ%tl9x}dz藗y%Q0s8q9K9kՒmr')P2$Y+&!KL zd IQT䉞T&OtH(7I#y42ta@Q 4iG<|37zxj7=xKP @ $a &z 1̀ 0P s a+:QLb@"1qHD* 1

`!;>@=k A0 ^.3ݽ6=@p }C+ \0K akT !LJ mմ-Gp Y nPyP>m > O[F \Q UPaJC>|f,B s0=Y`mہs P@A,^ɾ ^ŵמۮI )6 ް05 j:8Mz@gLp)40yP[5>2<+ǜ /q@ /"Ր(OI%oߘ7O',O,OI424 M_<G+e!B7 (½ {/T5 ëy^EHn|Zp8@\՘O"ӓP!A2qa0 !t ~'\_ss2>n8<E,rþq_^1^ob>ݫP[P0AO?_?u<N!n? DX8K4^4`v\3*YGŷyvkKH9X *f`Y",ri0ԉk0d@&i >$0G߃gdžL2Lb.PTM*TN *"Ф.e)̃l0P%&HM'JĒFW$ Jl>4M\n7,pqHq*u  EBGiG$GRn%%c7f`C<+5SR.avp'vx/jWK"ϕV>;#ܻ}Krvzq-ڝ3e. bvs?w.|6+%g^Da #N11n8!qb9-57EI:yvFbu1J5B ?n!'=,ϸh63lkYD m hJ#ǟ,GEz(%PT7Vt/6" CEP ҡ*`o^VDM"$(HYMqǂ ~!IHMZ) &$q[\Q!h-s97*gLeu[ !40#6Xh4H3,1OV{VbLd*H4ij^ل7C9-ȜK0bL =iT:{.Ao[#1sL.ӡiQ#D? }CA AҠCTrB|*1E( P& BGB(B>kR!怣7M] nP $H V9_&@94iDmmW1JYiRJv3jYZʖݗsu[~-)zR@4 $ X*N<6]0rz(GsdxL30)z@X+'L XJ_BFa $XpHEYnZ1lf_ڛnkc[%$]YdfxāorN*8̈́gCEN 88bNƅx5q*pE^/!7yUU͊[&S,lyy h|Nr]12?uSU:jtX|{X{OA꒏=s^X]aADA PCqL`ap;rajU.yIq -V#TE2J\|5/j@!yTz=8Ò0zo:Б< /\{zX|75Z kzW~}w߷>?~Gտ~?$4DTdt  k0t̻$4SBt TAc[u8T07Bb(3I4xM!D"301< 9"܎7 yACPCqX@J#4h2>Szd88n'G8*p <zYiC˅C\SHDI8KD ,oWDB'TT KC[ŵPVdDۜ?4cDƏS_(kbȅjB(D: h&kJA<X@I6u8 ̨$0hޛGL<DLrPL,~!{ʃ8DʃXh$JJͰaLxKΆMxNK΅L$TAr(!-\\zDLIK9:"qOA)ЊLp P Pxϩ, P\_p gpuB7`!0m  GJr`l,XlTфwkpH|%m]P wRA0 #}ƷBQPQ -4US _0o;Gr_PBph G4R{CQ% r8"ȅ7}p JD54D9f0ps2S=P7; ԃTULe4pS6[]3SFBb5c%ǃ8ң2!%Q.t >=E tP5\3z[DFnֹ@ ˻3X5!J<q" : EXa}jI4yCL bjP՚5NaA&FZpp_nf\ʾ]&W` p. #ı}n!uy\'M?HoM|ir \FA :x#7x'D5@Ayp@;hWUPr!d# Cڌ pK1 h3 HGrbw䌶{qc;CG)3x$AM96`9&PQ?@Gr%(F55ut~CITMAi5!9:C Gs7N$(N?eN*(B"4+2k=r 8eRs D-;@YTER?zjw)^Nuѐ_s^s\Lc`)c&%EzQ"`J*5HK"Ȃ.mDwv (~I栄jv /3^YV-rP{k"K:"s詨u(~$B7ꬴj+vEHg^&̦jF+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡wkCij(B":&&"ZJ$KDfEF̢E-Y8 &LZ#2f` `#E-RUL#ґ55o?z 2`ѐ$ EFHH nKvQB0|4&@de&2 jhj@ MԀ$ ^2"CeܠbpC\f.hK9@ B.e"֑q> Ԁ-nM"1,9%]&Ct:D qx dXh,!y N*1\hC(҇S"ygzFT!!jjaF\htpf- Iλ3 +_!j8?NqT?Qֳ>gFauUճa^i2RP4QְvM#JSaJ&d 'q \7\pp@t,iF+׽|9x d)g[*/_p& #m0Jhw5! kx$N xv#! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,! ,"U HA*\ȰC4JH!Ċ3jȱǏ CIɓ(S\ɲ˗0^y̗5 q`Θ@ JѣH*]ʴ)ΞN}BEԨXjʵׯ`Þ,Yf˚6ՅgѺ_ZgLXZ L`syqfzjT1ノʅv3[MӨRXgɠ_Wmװag7թ NTV]Cf8_ (vBذ1@-, P+3q?-`lțBalN t~kC"L䛏@|oC6Gjz ?RfYFZռOz9^w{Q$A @0)C&dhA C0Z :$* Hn LD  U0 ߁p-'( "r9C  Ѐ]u'0 % DP}"HP˩WM&n~ B @zeF x6 $X00cHA"@@а)L Vfx$әO`H9g &Hv`-ǡ" y@Z.`sfG0N483eW `-=IR<J)@L*= UF*`m T+Av} ğ:fUȾ&CQ 2S )Az) AQRHȼhA$DxA?ή'u:2&JOK >U7Mm@X #am?{9d4?PZO mYCRp"VHp+vS)BZZZ0BHHC$ wu"Nx[*:zO(Fϖ T?;{ $HD %(ezcRP%H;2K?(ҁVe-2}jaLg/[>qqޠ ۉFpk{N].P]kg"x0(\a#H$ْeF߃yT@RhA_S.?fRK%Sd6}V hHV @Ȟ|H|Qnj޶#H:y*s&Dj1mR vn{'MX408RңA ZA@i<0[ktr K8xl{%P 7m~ F_&؄}./'4s`~Fs~Т~ />P YpN؆y1S1='@'t^FWi0>K[w2d!>tjxWx? xx`uHg yWFP 7X>.E0~9^ =+ڢnj*(Isx$CTDDE!Ux>Kx#ی6<=Q%3@ ѐtdu=VtA+oAMQ%ڤN +,9OaeY 'c0if gAeZmnIWRعb 1]G-tB,0|! D=0  `*>ҐM@P->ޡ>46AN!0np` n6 0 ]@zn^ڲr#n#AӨJDf Au r-a 7Y--U;m aj}M.- >~P ڐ N  E{H=jXqCՁ$ []~T@t {L_/ ?⅞Nv-O.t f.ˎ^TO*RKpduvc[+^K!}G(ØP=q(Gĵ`u vKv[aGN;>ځ.ߖuqEm ߅>n~/ @mW?^?Em z -ԫ?~楿.H-o0/BLj˯@%.; Q8} λqgPB >QD-^Ęa 4hqbǂ$0(b?hH2?6$P“7widʕ6椠d˗1g| )M-/SCW؂phOs1hnAmQw eo0~ fƆ "69C_IKgi-VC/P5k–-(i԰*|71I5>|.W 6*}>l{8oj+y}sAK3tQ={M, :2dATd*'4`%d(Z(0iÒh0=RQD!#Nā6sJK%'R9ǿcyX:d)+RG.q!$'ǫfA[!gȁi$qg>q7DkZiрh)NBD4t( P7C#:t%'r [ E"ȉG/MCm%RA] LL0BE#bv=Ƃ3TiH{_" ҃Ї;ˣUpի`}#5k2f3uz}GTHccʰUUq f>sIl^ NU52gzGTLgjuj=`F-0~2 ?YUZ]7&vwxSυntC-*?`Iԩs ۺw}6/]n]z ׾}nˬ ILƹ&JBsjpzzs0{1wHo2t=hBЇFthF7#FݣiIWU)Q<#Z$t6PAn iNԗu5RV/k6=u|뇼[:_Ŗu‰tZvg٫FuBMjGz6Q5D=mpS&ѭtnn77l~A͞7˽~MnEӵpKdBцMp {Ix?P<'8RW<#e>_wӸQs\_p>9m'tSV8ӍMVZyNP97.#v/[|ӾwS]v:mOl7|7^j<,?no~wz.|^ˉ/x4ЗzgL߷?y/W1_k˩??|>c;9#?7@;?K9={98;3<T;=S붇4r#Bs@3BC>67n )BR+}#/CD97#043Dӳ1L6tc5+7:;<=>?@A$B4CDDTEdFtGHIJKL?|=$M=B[@cP+E5,>dYY|82B'\4EZF9̸X6}+FgtƔcƮ=)Fnsz 8BU p2\ 5,v,>y8S dA[Eq&ۼ滻֣Fk9d\Ȅ ȢKmȐ2L&A+ITƔT;3򫻎ɟ,2;Iݓ:[I@= ;4˻I[Bʭ29ɚ;I=tHztJ_O0Q5Z-DR,8H40I]`VbRQXӂ@ււp9hjlu`0Z;Ȃ/<6TfT}t-]Qg=WpHՅ6({WmW0܅#Q\`R#H-Ru5V`X=fHeg5"HOӏ ّדT0 RHJ(qԴЅ5`z]ׅfׇx֝قH٣E٥uVࡨ6P:`nV*d}(xcgY^HȄL@ZH.e瞞en傞k>ekE(Iָ Nf̈́<^U6Nmf)k&ͺɸͥk>P 0Q Ġn[葏ۂuΆgnmz^hgY~뷖)^mWdBidnKFPhXj ʡ8fDu) 煳R\.RVeomk ʗl@Dj_-th@F08d(8p8Ѐs;M~94 PbMhiY^΄Z>,kTe^h^ O`(\7A0F.ƾ6UguUi#_u`QAvSTlVwR6D[u|cu]^_~!uY'cgdoe?U^cYf*n b-w:l>^yz{|}m~2c'PPAȵ4Sx'xIaapͲxxA/Y|؇$)5a{hBlsp=0noyf{gyPs4ys"xyMAه=x7?{P{a@{밇{MewyČkgY{1_zk |Yhx#k|7{{"GbB؇nG}կ7! 1hy//ysF}{w}yh>a}XƗ/~}ڿ(~7owyps?}+hYֈfϞP|ÇO;as0y(>.D`o/y,XàYފUg[LduTn,J;-Z{-^ 9KG*mymۜ<5I[D8sV)%KQ f?4UDg4QK=5U5QdX5 Vkܾ+TcCI.ˎEtk fg 4]7 G6ۀzo]n]j1Ȟwj/MnK@S`p9A DuCt%~~V ?<|_X{ j+ X6YqkO#Ip`&1RI>秔n+=} B+-zZ'ȇJsIHeMg= B(Ru< . [W|3PG _b@4畬<ƉF#q 쀟B( q~JD+aMFȉzAE4/U/$1ѫ ` )AJa-p9-S!bH6Az2rx{争!aABA0%d샱K4eo~C.CPIzʤ1l-QJ)yEk2Y=m>eF$J2L@d/yMbsIl IZCs J]<\eȁ Řp5-֣a7|c DІH4"[(A yXEG9,:ZAbp*3sجB)E>b =+at䠆=,bkPi˜:b6z%q,f3r6IBB#衔CIβ}-lPv-S򶷾-p+=.r2}.t+Rֽ.vr.x+񒷼=/zӫ}/|+ҷ/~= *$>0,R!m#, +w1[# L sÿtV$z&>f I-\(~1clHo5&P6sO$Cq!ȶ 7Y#3NK5RO2lqyr,1YH\NB+n~"o 9y3<%~3-AІ>4E3ю~4#-ISҖ4Lsӯ峧C-yԦ>uAK}P7ծV֒V\*$H ,P4AA`D#( 4`8 :+n+ W! l-oA:.@o0w 0(VD "LTx'\!!6aM@Vt|7\oT A L@?Zp &pAJ 6]Rp;V]I9T`^RuW0H@A` +] 0̜V:0lynճL8&=6x u`#$;lX/H\`1Հ 2gZ<[: &((_h2-(A _B,A b&A)8!B&af'e_\ -d#LA-`"-) [L&B@e^ffD%*܍]&je&AT#t_2B_K+`K)x54(98Gh¹,ĸ۹KP$A$}VJvL@\|Kh\qPQR yҀDY\ ЀbTŽjcA<"tA%"le_t `$A [&DA?qC?/!BhM(hAc ěfvgF":)Dd F'A+b ?4A$$jz&r0hA5 AYKC g!uqMbP~bO % |˂(9:ҁV@Ʃ@AD=L"*zE.68 ) 0BЀsh  (v!ƅ2-d[Oo3%M}Be-!tgqi{PJA)^%V:VuR^OMm?i}\w!_L ,LPlA\L6 H !J5n(,*jji.ډN:(r @bKJ|(﫰P,/z+D$Q$-t3LS5l6)Ha!*@a B;԰F3JF>-&`a#RX3ajzQ=%0ůAkol F0AA N|PgA nAB%4 QB-t aCΐ5 qC=D!E4=$.Mtb7D)NUE-n bF1Q_$ј0QmtF8ΑuG=фxH Q4!GHD.|IIN{%1)R'hIP)QJUt+aKYΒ-qK]/La41AH&T2M`BG5MinӍt5pRF9Hs°D;Ўv =/R9{“S\ ~y(&Q(Oh6ZPџaZf=e-ddgRFaJrNX?fJӓvZ)0Ҙ9UOY*N}5.i_|:RUݢQ դ.ƤeHS:-P;sKdW,Fu>{[Uj zʄ{%+SzV.(%wkd=UK,4V.dg_+6@:UԮi6'+[ݞћu]w\5q\.us]Nյu}h[0hLo#G\ wKd^a.xQIXB/s q8`.ݐs)_ y80I<⦵ӄƒS@9wyB̅ #bSq`~1Ǽp`.g1!BatinjP%>2BG@G.KE@)감$KK@A/= tWϰ` b5-,bclxrJ0FBUP0~XDg@شSsdBV|ȰE`™2)d8!f A0C`8=%.Ċ=Alh"˒uhmME;-## !  `! q +b P|)&4ȠAjDb+"@ L0o!e"H p4AЎ80 !Qy4Ԯ?/ a n!jsq(x1~1qꐑb ;q+"+ NN!D 9r&boj8ځ #"bb2 ЍbfR Ll!~B"S/8pL%;+/#2ϊB!/ b9 :"!" a"(!0$N:, hqbV :AVanpIn`*,`&"$&^@ t&1WB \65Q ! !3AV.337 C4I4YeT5i3ebsZ:er6p0}8p! )"rB<֡ߌ l$ "AvBeڈB!#3$ @U GCsaE!̒ap8d`"3) >h @|,Acj&Ȍ̬%cg "A  hp=ZaDo@$ "@ FAr$blD`!nhbx,bB . .!TxC)6rJkhec< VLM, l@ BJQ<- *v )BVatN(t"  Bp`bVtp 5Yk`YUY 5W*a!:u\\c;! 7/i4?!+w=!Amh, r   bGddtEb'#cY@ALd-"g 5UҀe]`b3?Bv,oADI+co0"m! I? ,oDP<" A 4zhT، KiOC& ٘|0!aPBbn`AF@T@X-N07V/fY`5Js`SY $|gV ӊX.n z[U 3{!rD`Ng;̂(3zocz7e {S~A$e1;w}` K`g.\/ *aoaoN(b`!7 ɪ )@~`!i`K}6$V|N=EMU[Vh)b.s%i1,Eh`a"k)Hϋkk>t!rz$FV"PBgVot`LE)yHN dròIr)50P)0j`jST-:TO5$f6/‰)rVU} xxib Ie $py/h2$n 3PTF 37o 0|ǗV|ӷ w)c9 <¹PW8ܴ/2/RuhCa޲)`ڤQ QBT)j@@$zeE/ d.T(Z="g[vB傉kx)p ab$hxۨ3RGPvAQ$X "!c 8"Mߔl!T|l.yYra!7)1LF!,w!0w’?2Y$Ԓ-'.!j`:S$r,7TɕU,StY$6CW+"y.y :BsΗ3YeB@P)´Q:W ; "9=-B'"X;}S_W-ΝS."8&`b=rK€M0" :FFg!B-h v aa#.:cGpoĽ[?Va|"X"\"F{o%u$cz۾lC i)b$0ax$Jv!":2! Dݼ;M.2r#5 A;B:nЈ ""UCJy@$."du! dzwP"WW #ӚD {:tP~0 uc{ A$P[}oӷ-& |;=Ae=G=+{;;`ֻ." , cZ=ez}Y  4tqۻ]Af{'haiX[>y ۹}އb ϝef&!)|o`p΋FQ/"K)BnP}`>2$mשɍ" F 2 `$.to;͸9ΞA 8n Lr)"At'wHk^ d>N!:Ev&$=:l@0 8%Y6&2~s"WՙA{1S_K^}?s33P s'SW?}`pOշ焹- $?=*3^-_1۰x BH߸9."4!iD\ڳB<) Bz ?+˫ѬbvaO߱].ßHȿ?"׿N񏇭`&!h hL,ZH#Y; kq@ֵ \L Wʝ*CsГ54!i䥸`IM;bUz XSl1.d)00S 3p݁]]kR&y%9ǺC<_<J6FLq|_T@ӥG*5xPF>~ h u ,JʄTx*r)F嶓rYqY;pFy7a(|? 7 8VZiJcJLRClAD:Tc&)Y8 &PDd'IOVT&pB!9MaɎ55`)oJA_R ?9̈@y0$KrqG?iI`ҙT4'A MF! C PДf>{v3"@F'n+ U+,1\ӐT:"9A\EA7Pàʅ&Vs"D)RDD(S^T5"P-,u.{zJV 80SJ6'kpb 3xԇ2V}D@p,ͪ3{b`m4@V+/k U #y8aV5r#B'̼FA__* B䊃jx )rTVD87[Yˊ>K|!~hc\[?gU4) ʴh'ɄЦնg!qz*Mŋ%+CGA%=(+W CH/8@ h`Yds X $Wj+Ńj ! , c&NI5*\Ç #JHŋOirǎ C ȏ#STYr!D(p8rܘ4!O3VN!. L\FM=_FDY5 jhddQɔ@;ҡtIUL11Ť<$&-klv7^ĸآF ! ,! ,! ,! ,!N 8*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ"-cʜI͛8sɳϟ@$ѣH*]ʴӧP-իXjʵשKٳhVM۷pʝK7-ۺx˷ߎw LV#^̸ǐ7*L˘N̹ϠnMӨMͺרGÞMoͻwܾ N'ȓ+_8УK7|oWνwۿO~qӫ{~OϿ(h& 6 tdU8aL:aFUaKR4nNhЉXӈ4TB;bc]?BBAVT$9QJ褎.S8e RZ)椞f)&AV ",&;a*>jݒ-뭯nۮK 1Ik' -Ko).o(6v~k.c l$ 0 𨥮gp&A۬o:0׬kt|3 1R 6f|2R+67n#wds7L7={J~K5Gʷ^]ppgNҲ+ lxܰӚjw뭂:4N:jdž-_ HL:'H Z̠7z 'B)v ]`B C" Op#86‹^b"X (D4aGhn|#LF)x6؅/dF0d8.vQt}Og-- ()>pYڠ٫vY C@A0`:-ync3C$nP < _m`9"1bN$w4 nP{9KӀ)(Ud*D N ha) ?ޡx#p>/AArP+@.pǃH]zS"[}*-#hA6';`-"FHA&0g|v}`qbx^Ē}rhH w{C~E m`p~ `&h2H6v%gI t{A8|4Ȃ`Ā45@ [pQn#7wm!g4.p %pg05%sN'.W~9Y <6[Xah9Yyٚ9YihY%`Yi͉B8@ pY։Iii@^C@9?4` # 1 lˣÞyaߙLԹ3FoCB(Uy$"51jՙ#!$@L@i9YAg31 pv^8j 6a°`HjbN&,qrg=20I1q2u<9 KcZbz: ``rn r NR{)wNzp5 w*)5 pрH&osZZ j ` ! @% tv) P0 Mw"ک}*u [Y jNp`JjjTRf4 b&护\:NĮt>Cu*tIz.<U:3JzE\%ڰj: %y"б! .% k€ڧ/D1k<@\9 а8 0ڱ4/+b 2; ƴxE;(#!߀@,+- ; ЧNDPk`֐Ѱ`*4Jv Z"W {_/.7R>ʒ3BMgD_B+`{004ٰ rKKoEEb ,{,zKf `4³ Jv@8KF;ƛ"лY1e"3br ل@L+!\L5;m" vf_KUc;4#~!3—9Q$ M4Ir0,[O8,l-i!z[sE-[(O T[j+1Jl2.|55 ], 1J ,_ryS;ǯ,A*#]&0 Ʉgzg!hk9 =Xonm.bcؼذ>,1@4`D\xŊ7k;۳kNj*怜GH=`Y;,%U-!W^j[ `!P[b⦡">ɴ3 @BɠӹXNDS:ߗX/VBC<-`-.u=<5ʿ'+C+!R㉬jΨʖ΀+.wjZ&nj"8` ) ͼ[^vܼ>3ゾb}L.̾(# ) u<[ћ4^jxm'> Vj_C&,IE pMBmoN"8z~,hX5>v;:-"%O*,.02?4_68:<>@B?D_FHJLNPR?T_VXZ\^`b?d_fhjln2~_  z@xo_ zop~& A*1z`b~٤~L>P fmGp v2`p_FGiM`RuP|iPvm^L)Pgv؏G \EP ^+pmwwۏ?TDPBs,RiN>*@h&H%MDRJ-]SL5męSN=}4hEl`XBL*a (*e("r,FHe͞EVZmݾWܔlEOXJ½} Ƅ:}y$KWfΝ=Zh:%B*TqL?f)O6siōG\rf#@]U(u>ldPr`f )\|ǟ& 7ݸ R D@HZ'z*N("B HB(2b1FgF˲ +j<@H"e4P2N@)ƊP/??6dV$odM7߄3N9礳N;3O=O?4PA%PCE4QEeQG4RI'RK/4SM7SO?5TQG%TSOE5UUWeUW_5VYgV[o5W]wW_Ua!aO:V&b:d9FDp ȑg})RQ%hY-\emKdd$RI ,%l=[}X7ـ H O.(.n r8%brJu$RML9F9Dt9! $g`%ZJ7j]cjr% Ҡd :耠 N lFص~t“m"qRT> z6zv ƑD0AZid0qfuA`gz *ANظ >yޥ#~z`0ˏY5 d@H FIubho0a]uOM?:)?1v$x-1ǰo/|Oo)~}#:OZ78ȦB_7+osÜ~ȿq_zI>xYww^Rq~Zs_x>굗x<i oś8~w[~OccW.3+['ƛt6[ s@9 @ T'S<;{=# $lc4g !$"4#D$TBZh%BX( 0b ,–PF(C8 ™81TCѨ؀X*Ä (h ICQ!4A@S@AȄA@DS` ĩS0LF@L:@<@ZPjKD>%ZVD  C HC_`4I$8`<FSx00HVUԍHRD2A(rx 'EPa~Tj3`L@`> jd)@>h@8ń(G|yȖ@|G+Lɗ F)00|!HFH,\SH0@Z$Gs$ȡLąG  "! :J@<S$'`FȊDDTJˑ@ hjڣ#>*Dʂ$<˚Ik$LZ I0˹̄ǩPLa%D,FꈂJ$LL`*hȇ̄$@@Gɴ(˜KXZ\vzx" LL>wFFFsk\mFd'$$1O4(VlC8X;DX "Ҁ\&|DTxMD Ohi`Sifhehf)on}T&0FPFaԴ^~+j~_HaflRh05އlZulGVeuc=eEXe%>.Zo^nlm&cFA0Pmf.m6 >\mtNvm.gfԆkV&`YN]Qj`c2l7^hoik׫FjJly> W{h}سokTnqjjp Y7j/^\|EMTq|ox^q$7o8 /mq%ݟvc tfZ빥cp[b1d6Aiupf$NU]m=fưXq7Wo(^gܶk'^_fs@gClscs[[aP}l>_PptY qWG@-XjGrEu8n&>:Y-Z^s(awUqa1byUa eEHo˶sjAVZqTXXds^e]j70^sc稜p-*OW7 |wwtmo郗d#.xwzZjsM'V^^owx' xy_'xwH͝?obμ6ԍm}EZ߮grlTk0YsW Egt {vAmۑ7XA0X|`sUy@efq{r@-[=Zy@߄gܳW|P/Zo|_ol.ym x|g[W? G|_||(e|fُ|yWgfOH||=~Z4 tʰQЈ{ -\@疈x,h@"l!Ĉ'<:7rذŌGN*7ĨQ`K#c\i&Μ:w'РB-S=|fT)Ӧ=RXj+ذbǒ-k,ڴjL-ܸrҭk.޼z/.l0Ċ3n1Ȓ'Sl2̚7s3ТG.m4ԪWnlra3m69i}ҞX\qws9ΧS'+rد/MP-Ԏ8ۻX?G/z=>} W5XG^l9`t"bHaw z U rh Pyň{38_ɽ(_o6(#u'#Evsg#iB di{WF:j*Iވo:"XJ*Jh"٥NTj':ge2rfy**R+7 bjfz);').맽؞+.|/B p *U+[0cǟZmЂ1Ӗ̫#+mu䬔'<;i+³~/ijiv ;Y/&N<3l1ivޤ8r7 љl3a+8=nrm/˶݋}O-*~_;lG'-銚Qy .[lvN^[la֘؏{{¿撛J|%~Bۤ5Qϵf?r)q?m}oIV ԗJz1Y;#J03(C S r C(&U<\D| 80(AH()0r9,$A` TIYsc E"J'(CPaSQ#DZpB>J"yHF&b>$?p\g:@,(R #B"j Ozb98

!@x0V"ZNuj 6Mf+zb#-LQ ՜=h 2H5T:Q# #Rcp &A`Cr&]BH<J" F0jqIJ CIY@\?j jPyH.D ֞-Pd-* BW.[`zjAPWC"T0$A$HA|. 3 j02 ]7!dd")ف)XS41 "]b]E` NhnJ`\"X M[ZUu/|`{/?b dܠDhb):cB@@;v G$,201Md J 4Cj #2G.raD8,,҃d >roI , R (pg2΂Ӂ!.x99(L_0 JiCVI܌ $0 -z$X?ZZ 5`X qC`Z ilsGq;([cJ *6X@ w,,`HAr!A1[@DX&R " Nad0XހYk\'k00 rHNG)&ހAR_7kҰǺ*uY%B{SW 7mA1vֵu )aAn\):A cDHE"(ؾ$ pi9%=BAր,@\a =m n!"g5jg-"y> qsٰA5/@؎P HzBN `A"aAdXfd+.Y@"A|)C@#!$A@t;>$Av?x20'\J+@a 2".~^8Db=a.C1*\\\1V<# q?\qi.uN8[-]qW8vB\4 }({05A&葼#<7XAc$dr ?N* $X\]:bC,UfΥa^@%9Da%p'D"&B*ԀIB"$, .,,1[xYA)9=pCC ?ٝ*)-AQ~;?4@ P? sOm8"B+TfV_gAޡB& ]< \F Iu|RBh_)C tdД#geb6`,RIfI%6Ă2]&[[g"nݕ%q&@A*nUabJDBML3G)=!EjQC5l-cRA#LQ'V ܺxaA~xbfCD+6E0CD6F,N@>>d,J@LQ#B|lz=C4~+O,AA|C(J ERl]J,AЬ̊V,Īm2%r>T0@,m? 9D<>XCECV@@R=7R ~><4BÂΒnf>BR}C.ŷ 3UDr?xƆ7ȃ=dCl((9B9<=Dj[Ln^[Ԁ0(=D eo~+D hڮCį7$o>;.z/omڃ >DB8Y.-R>j#Cc0D$Nm2:VF,rqkm9/;/67X I "D(CgY7t;nC>g[0;B4=XEΊ1? _Dmq q0_;1|CT2 r,?p>C&1 #r%1B0Xұ_1Ё<og+@<0?0 r1%[/òΚK(1!r,?1 1jscF4p,8?620;17BQ jA>t?t[B6*'SȮq0C󦱺 @ @on,>oE@7D'E{EC@)DG'35@%DC C DKtCȄ,H ? ctjCJ@sPO^C=-BhAچP[7HNGk5Q9@u9s7BK.o1A(4dC($f[`0 k_6 v[[#1 tBsd>r;_=*hR7A7C(.nsAm qMDzn*7zsvvc?3AxCڃw~,cz7C3/RdzwG[K`ww] D9.I18>8?xuWutJs+=47;@CFЬ=7m3v]AvAHr9v@Ch;i&Av>lDmxQ ̎E/5`y%FC28yIvwӯR;1s;7Dzr[m筗'U+Ǻ[44{mw6,ӴC\bĬG%4,vpClwϰNzCzH:u  D0A<0g@p[639lz;b9s;AE@(ć{G@zQN$|C;/t&+3B ;*sn{]??{T4<yZ7c<Ë7,s,|.>CvrnuR g x#0ojnه?X.*#Dz٧,ڋRp.1dzޏ=mD˃s=3$E}C\*;·v?;Copw>ؓCJHw3E3#DCƂpJ~mHOVnOEW_\YhCpxY0qOl?Dr>E#d5r????@8`A&TaC!F8bE1fԸcGA9dI'QTeK/aƔ9fM7qԹgO?:hQG&UiSOF:jUWfպkW_;,KDU;lڵoƕrŁ~H`ېX5&h5Q OQL1gQނ-wÍ(YY uROuzrU XVT͵̇!vj*JЀN.8`ұKA,yȲ`C\Ty㈁ ]>PAރY G|ѹB$|h晢&6](!"Ƶܸ%[G=吁4yD\ĩnqlLPV@ do(xgQVhhnC\g>ZsXыBZZa "!_-R;M wD.-ߝ<;Dp ؒ{Ş(WZ㵮 Ơ %(S:J5!ݫ]uUAB׼dO 7`oГIA ,,d!{@D9$HO"*;s* d;\H lf@(RCJ=:y|l_A@ ji@.׾o[,B* 2 uj-a#Az(qE|w`Ġ5VH`Mva O |wtaΎz$9\#Ȃf>P' ɉP1 "A‹dABMi#oSeM;?BRb ܆*s7(WRyڬR-W3\ҝ=Ub @) b)8Ndjзl*dqo$q"*/4A%.]|.GDOk JE1Qjfm3#ٞ*3 lEsm)UWUUb{p%{<fDP 5 &p1-#rуmNWqWί+AKmֱ@_XXus?" ,hs|(JփbݸT;]vm%PG<=iCD1|2WHb ! 10,(;X\fTdFƴ&Q_ԴB7uCN5B@Gdo4޻>Jom r_CY9*q(49p\n C y9=^!80;p$ &==dV P ~Xi 2ul? A/QzAb d 7 4pe T `i= >>=2._q.Gq!AB. 2/!y /oS ځAp j/-B&"3g3Ay.ȡ>TbA!mraO! M]cE^N)V.sެ 8r`R=3< ;' S'Wa Qt' n(Toݨr@rj@ ^*ˮba/D.}ܱ4ae_:v]n=fۺ<qe%`C5hU\ `8,abbŜ:&>襔 Qg9JHR,} F<Gw^V,<!"DK@(Hjvw20}k:=WpTJ =MHt۝u ٰw=Y; 34WR9AEX=8Q2iƔL| Dyd00OH5eT!M P.{`c+˄<6pA\F >yfMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨNWVհyaևq^f(`8І$bj\C-9E=e7LD0살0 8pQ yFl}d΀ r !( r <$݀/e#B2au/ncCŻqqxd\ ʸx0.v6VO|-y!?;>bq|ܠBC=ms-qga nEA\T5IxF5@k π7C!<:A4 ׄ(p E  D n@ .2 &w{~{qW|~ɧw|~Зy0}w}}}ϲ~{'燃2}*~'"pQz&0lp r ʀ"bB~wt'"pс$Bh%1 ҷ LxR!`~ц0uxj؅l (iA np :P Ѐp  XPw aZHtAcwf(h(|Ȇ oqh({(H|~8+ȇxB0-7@!{W8‚z{G}8X8`5 q\As\2˦h?(A@ y@ohppv&yp *W⃆zWXyhxЏ yI)BC,0p2R(8GP['x WXbtwrl3ɓd5q`@prrwQ~,Ip s9"A YT@ mܐ o igI'ɕ)x`cɗ1 jɖnrw, r9)J5ndQM&I{{*WٕrEŒN5L{Woi$qCG Ia IO )iܰ9) 0II}YÉȩɆ=)Y8Q'Is<7I*36) Q2@#PL5鈙*y0yq<\1x!;JswZv {9pFj@И`&8C,/1Z5z7ʓ9*܉8> :{Czr'Joz`0u4YfkJ.@ .sY,-)󀠠z7ù@@JA d L 5@VZz aJq. @ :::2pR0pfpPPz8w8R:|!rGr 1ss8rǣJ"KP'uW jG ;5 @ d w n@w Sv ){7q ǰ-Zrrn"۱|˵ڮd\{8eaiwi+>k۶m gRubj+%"Oje^{bp=f:hT*dи8۹fv70IsJfP:;k[빶{i{ۻ;[{țʻۼ;[{؛ڻ `˽BI{4p+p dp{k  0tpq M砿 ( `sq  {h+[',pZC-QMk g?nԾ@tΗM8pARN<a.|Iqe.p}刬+u@xQ _>aA`Hѽ a-WCށ.U.&e(SLŮ W0Nr{̡: +<Ѝ$,P=m{Jqyntҟ®Nѝ!Q޿ҎTN˾ ʏˮ^.BX.).) %+߅.ՖMx\F~.J!V 쇜 [S>ҠkNdB/';_=?@- a马 5 @~\ Wl,1*̾a{mO \Q#M]9 \> n[[w=Ȗ м .A娯ȏz풿^޿Mfm\M]rpO; Ð:OcѬ$p .lC.58߰s{PA.O=xQWj䡏W7H8KK2NsRNr (sʵ(cy2*< FU#䩖2^`n0k鑩]eV8e(l ~mm n6-^vz6+M20 d w Ӥޠ6NߚRfxن攁tD6ҕA?j m|ә)KLFt ~Ϸyb={] ){m|OJ ~QOe4&ÁnLr &Lq?XDD@9芓E7\A&pɃ+ okN Xd~H  nLb8B͆"Ԡ69[MF1  :phAPКͦpĔ  %! aLGp u"fy_<mp x-|KeqH6d jPmDD%A (#5IH2Q4d&7N~& #1+Q٩;'\!!1RL v9B`=F)MmI5x,viН7<-]\) pFJUq#0p蓞HxI4ʸDDXO7~k^2C#92)mh !EĄ mR '`` @  Q4T< "B pa)|N rZ"Gj#&1 PS*#<uN^"'[#mmQ[I Bnn\3C5*:Q&; @r^U n ɓͫhݨa#p{ʤa'k_;D ۜwOyjV,`@ ]hec&>mhYu-N}&)"-H7"#3k:aƉ/*97Lk`n Yɘi\KeAI[7 A`uK^ 3e=+>8U|khj [$ pe`5{S,\Xֳ@ ;wX5.CYL㤱$ L;α5hms7;A: b #5!2Ukt[7'sjיȦK6`?L>IAxW uHǜBYVV]#Pnv )fJ#MdZ} Dt Q8>ע4mmHkYGR2 L b{ u5^CP=nS6>QhͼK 7y~ 5P0p dosf7xwOk9a ҁpՀ& 5)x `ЂK?Q쏈%ŶXࡱ4cW8t$M~ģ+6qH?>d7 JT$t_ʼx(?9^XJg/8 B\3KL2mi#=pP|KypIoASɜK3v4O`?삷7b [ɚ9" ((ӜI+$ρ1Ձѩ#й{=W{A⁇3 :YA߉ԣ*[KT9:= |ɰ `3 S82nH1;:@3SR?01)pj9vP 4lp0Ѧ:`C1t!AZDTD/ `DiIjDʸDIL';qDMEZE j&px ]CF(!jEr4'hmoiF<)6bOTpq$r4sDtTudvtwxyz{|}~Ȁȁ$Ȃ4ȃDȄ"Eky}i|ȈTȊtJ C#9n @4,A 0,s @;4 㸇a0ɓɍI0ȧ\'{뛺xpɓ&YJPIC+%9ķJ'pJ LC  pK̋ ixҸ;h KPчw0 IzL4@zԼKwMXM:x#H !N 9˱$ Ań#?0{0, Q͗`MTO .ؐMԑ$΍(ΈDʩ4{ }XXKLL 1ݨw zOL  ,`QP l mN ΂ш$ T 8 ; Q퍉0(wJ`H! Q}sѢ 9 R " $ %%@=K ʦ!R@sԼl9ҮQ@R` KK-R8 !SŴ$8ްל>M$B+ xiNz8<(Ӵ܈O$Շ;adp]թՓSk}OAuרDاp( И7j̟@J HTjؤ ~ҧW>Nw(KejeJ m{xQ3ɗeM}˩XׇH%˹hy KXKؓX}פ 4Ӊ-DPbٍ,O` ] TZ֌ e=}(Q[p?pؒ}YUTVs(ةΜׅZIø̥ѕ \[Ͱ7iP1WsY=hXXKUW[ ew(0mAȴ:׌ޘѤ PYm(IF% 5 m]PS=^0^x^ |TW[]Y W7¨.RPJu_}EZˉ{B xXY]A aF´ `P`荚֕YOʘas` F=RE >bhaD,a'6KT.x SHސsQ 5 ͈a6ddPѠ:uJ a:S qRR2>+5d $K1;$,YWLIam" O6lMQ>nC[^ZէU0PNWe)M-E`(.iNaϬ Hؑ@)Ur#NLƒnIlkFno%t {\軉NV8^8ƍ&i\T(VT O}p阖陦隶&6FjHeioyhi&ghLVWdY,F͸#7ٕ6, Iu A;ވ!-𓶣3 q]օ;p:,˸]؅]H͠찠hkrfGKHe "C˰m%;YXlhT81H9Ќ  1D<1iIA ̩ƣ@BY oqFAA<$ʣ+llTk\Bq  2 ZB  m RhL`,Ƚ^clL؈q7qH"XDlYh]q(^D؅c@G];د&a趿#J%j" ;): Rm- lN5ߩ#5j(%4"%3-:Y.GHM$O X%ɠ%%lz.%`q?:eb>& &p8]D`ATWuVYD9(_(u\ul?R0pN#Y qtZw맩("4* [`)?@mr߈@ Z2*|w ; 2)Hj*.6*iuV:`TXr`_xbOulL(5p퓈++⫤vza,ǂ, :1 6B؋24i0At@Dz9ڲx8.G®/K/Ħ>R/F*/hxy ǁGZ`G߈§5c7 |#HA\Z0yy0 0 ˰ LQO:I GQk܂=:u9 ښ#K2Ȳ H?/6Wl3C3WP3l ?k+|Oqbwu>Ѕo߈,8`Ȃ;IЁcx|<,hlmˆ'Rh"ƌ7r#Ȑ"G,ib (P# KX8K^e?/fA"w 58e/]O5C=aSAȒRLAYRq$P/ѵ`)#CyxJ\v&"p?i8ʗ3o9t6 G,H"ء cցY GgRLK+K?CmA 4kr1UZ@\i 0 ԗWL&ኆ)fY)T) Fr8P?ElFPRge An'YA@Yj%]z%H| $))6@7TL78C7TГ@VsP@#0{hD5\NW^"ХnsU7iPPmM@Z%~N IQ #V 00DÃ+ 8ACqX)pԃ+xRRɍ!..TXO[HD*rV h9R /f$1&b f\ `|2)2lp@ pdfAPgM٠<KY%"l)>(OcTbG%\+Lft0A__}N`Aq C(B:~d(pl~&!Z8’Tc䀈q p-f(!Flݾ#%lC)RV"-r^"(1f<#Ө5n|#(9ұv#=~# )A<$"E2|$$#)IR$& }K?Q4)1bsDp0q{ c(-T <@ J %:+^b3 iPD^AWR ?f9Kz /#NLS gD^e*qSr6O…{tPIjNDZrI@co93J]@<:i”Ap199!{h==qS@A=JKE*{T`*=:#9%QtC]VX:6[88= B%0RtgW Ħ8+OWT*YWjXu1,ߡ 5s]Y,<4RV15@#$HQ+kRQGche?u37ʈ胸,93ЮЀKT&ALՆ!؈ .K Tw٬rC}(B~ d! PQ0p `p x"/YxxdO_VP 'u(1a-M}w.=NxRFo dpݣ縟4Jk1o4sHI4 ];^x}0WM4]He;LhZbЇ[Z3-mh@у0B㴴޴' oY-0 r*_ rMNZT>uOk]{zgO|7WP/V".A>TKZȞJ'ל p,T>^]45f Oy[B Jyҭk??A,r#.yɯyKX֡U] Ė TM Y?~MwqtyR.DEĩi `8?бw &yZ-bK4=#pub~9&v 1ҜA\5V!<.}SGͣ+S1͒4T0U,J+=Snm"AP_fH8V%"T RcNVCzeҍUB8-]0^FbAf%!dt MnGfARCRfd$UdM)J\D0EUiVEnLN0$MDJF88yӅ#[ I[D\Z\ʙA\]_Ё  &s%`.&c^^UcB%t(fc^&fffn&gvg~&hh&ii&jj&kk&lƦl&m֦m&nn&oov"@J8Fb+4>@D@HE] 0@) āx?Ar-"G'j' t ̌ ,˼|Z*bjmd@ ڒ"M@bX:+:,JD4/A V'ݚ.'PVO9TlUpJdVWXFQpdOܘD:]LGnnLAoqHA Du\Gv whGXRu@ ^|\ilОFGllC溬z/Arf$F@@@q@)Gjsmئ4@Љ@pG |-Gq*  Kʡ (@)MJX I7çZzʊzťd4 ɶ1 2ȆsrDk?p@˴T I(h 4()FLHe CthLL->ͅ$To:kG `|=A~M'SDKzuױg׾{w [ѧW}{Ǘ?;f׿ $@H,"@NMiP ) 1P 9A QI,QLQYlaQiqQy R!,#LR%l'.Q!GjԻFqЙ<) S1;Nф5_M6pLҌN7ߜϸ\( TZ!80CkJ-3R.7N@  dM%?Fӂ6Y/H oZ$'9,LAS#ActId֗)<ܓuZ(e(Tv9ftZ;.@Gk) .3DݏSւSVD'2idYhla$H6S2A3҄DY' xpSqASq%<\#o wHZ%-8BfF\p {l܅#Nn MYU$ ?<Z)"Y}MA\r\Vr8!QL! >;uh1>3!`)c8Q$cd#!LIDcJ"< FXdj)(`j!1VS皧EO([ '80#5,¼LDf7 0\d= ЊtI@)"A RP?v` $$2JA⠻m}  Wāy r*mm6 xDh T `bE,jh"$ Q6 !1Bk!:yP VAa9Z֨6#`GE0q(W B.z!0 7eH P($zGsAt 5C6^ꁡ(-}|( c);VBקb%2lL sɪlx .Hm)L຿=Z*R]7#`P;j`XnNK&ԀĜOx{Vynx\A >H6aB@8wDchmr'$nmO'OW%xı35D7'{>! ЅuK}:` 0ӶzZ3*)qS2a!d3T# fOlZP+f-֭+nr(4n!\& "f.!P""2b(%PN Zftol+^~,n@9`j`_  `\,p"$bB$Hj"p!3-OWC^= BIiʊ(H!p|KCl2+ɂZ+5O++Qs",.$'", +*/O!N$̢/B,YD0U-0+G2: s?iOҠndaB3&fVn%WbdjXΪX%YjY2&ֳ=Ƹ?CZD?oMj-EDA"x/kNfa"fb*F:=mEdI-NfIYF`Tdܠc@MQxlFmmpfBcEf'P{h|}}GX~Gv8&MrMtNnR# .5AQ7?uR=uTqԚ4TTQ/A-TKTJeM!J z:)6 HYazzn)vOIɘIODQk捂5REhڠ5/Uu_TL^_o/6_t_Q`[u^^vRy@DTRTFV[tZrWkBjFGj^]LfBu tYD[`ZcR̯R2FbS4X3Se`Miwn@BiRvjvivijmÖjvWQVvTdcc˂dovoѡ&jX썷|Yb$ /Mj<e[`JahOZ,QnvF^u5l"VnmUo7bͶQ\,tv_m^Yw^n)WMHdSQL帊TzSxFf|0M&MpM[m`~gtKO5zb1klvRWRR)x`Uvv_c5vx XVlMxm7VyDKMWyIe?i`.{}&tfAv[q`NS)m }WuiٲMW_ncVm  4e׈%b'؂EcT-hK݀^v샹&-"/el֠Oo)OؕwyxmcwWT؈Xvm/Av㕋5&@ߢFob 1ǂJ)d q#Q 'T$~$Y5bi8^=Sm~Vk'avGٚ;`WUakbtVQNAW@x@AR$I$ц]QyXg\rU&EYn2@'y~2R)CMttUlRU"۹KǴQ巓G~Q8xxMۙṚÖyLOz;j5#5&nዯ9?Ⱥ:\] 9sӲ1=A;E{IMQ;U{Y]a;e{imq;u{ynq`ⷃ{{;;a"[bfa śۻٻa ;![{-'`!|!<%|)-1<5|9=Aj ܏=>OPgf!Ad~+ݓ]9d=߄ ^)*z`ٛ}>ݺsI^q ,>m ">Gu}9- !!~m!aaa!ay?ha@2_!f!f[_5C_]>j#>  S>Ş"^ b|S&]O;! Z[o xݞ9|H B622$FDN!{tDJ ),!>xF)McŽ FsAYZC=ɑ͝%Mz 6رd˚=6ڵlۺ} 7.[9 ҥ1X{\@g4G`†wK*Nv4Wca/A rV4 [[Z$yjmV„8$!3^]YuaFǥqH.m*|@6xsý&؄kWX5O :Stpw{0lU9gj-5zIS(3]u]|ady[}WNwA=|^S^vrHc6ވc:ȣH'\dAH{s]ϒ5$հqy$ERFWf5,I}l}# $O_@Ԉ<0_I;>5B@9&'!rfy hC-DݗauZ'=!Z6*]cb馂.uSqAc>0*nU Jdn6eW=Kn枋no(e`@OV.]{oV"?fR/d0ΔEƢ6bEi !јc7=tDo7Y\l@,/ˉL%TI$D`DM*_Hw2ކU@]Uvy[< ;{(.EcYhת:7Hm쯺~ x^i qٶ&op[B=e{v9x,qHO7m?U p뒜󻰬+SIW`(9Y%99@H X#y+p.}ѓsvy,% Laط],ip*zVe=d8rM1a*| SfSt m} er" Hd|& e3ߐO}'1 @҃}pB$G˂ 2g #ob1GJƜdl |:E'F4-Bʅh2X>B146 I/ (@T4)͂@ub"ÅtѠL|K SRB1=R@\1}%}$698< (hGQpCz"2kȔt,=K$F )ni|Cʩ<4ԃo ^IGW 6ꔧ2ȼ: ylhzZ=ڕ8#aHN*G9pRIPڤİCYNJӾ5vi6R/y,55>D'"k$L|*ԩRDd5ki:Uxkj@Ӳ§*JӮ֭0PKTIf"EN Юoukadtmsw5,tt1tF dBHn7ltϋw9׽'z.4S[}knupu<\;[{}ç|p?{KiPt+~on{}w?~_9ev.G==o:7ls3~އ~tcgpvuow~oF{v~Fǁt?GB7'q+sL{'| h`hFsj`ǃ;Hmחt&lCg~H|t6h@XzڧovVJyHUx͗msVwcRWrauik#Fq(s`#OHy{nx#xȇ(XfhȈ舏(Hhȉ艟(Hh`~p_˱1sקHbA6be i8d&hmd! UNV@0: %T5WW\l2]hq*oVp }n~fl䈄ƎjQ%T菻ȌHt`5`|?LR W1ZRRS6tVHftB'G{0hzp$#)yGzB 9HJ]" AAp{SLt-n7}&hWh~ogGǕQI.xוS<ٓH0I8fr-7YsP؄}(hؖ42(nb8ԘƄQ0ĤxYa{IӔp;)oVxgjy9IQ+Džh~Wٙ8 )$>^ qzY')ǔǏhˇeə`y IzyGw #Gx&&q"a Mw;z &MV~9^H~\ik[ zv{8F,`=PFdMQc(ǃX )ܙn :6w8*"8 ؒ#A;SX1\U\ ؃M9zpḒKR !WxgSHgpxm*o xזd#[7ƙt)j{hJ{Iɨ-ƣ5Jʪ;" >eרw kaڛ~9ت*8*JjNJɪʬ *JjZ7؎fL Rp P `#`p! k! @P ޺ʌ*H{J1ސG< 

ɕ r ;0YPs\z@5^ $:ߎ}ː/0_g`݀!np :LZܰ-MЀEP]0v NϨAܜU{ p8@'^`K ` 0ҩ l@Mdߩ8m^0d,9nƩQǷ ا;7<~P`^np}I5ENX,.l Q֎䆭 P~a]NV 7X~d԰ A橽ڇQ |eprtnx ϲr@@n$^P .阮I̫435XBm'4>8]L{&}F`1!qMMǶzplL|n Ae,yϷw?u;[@XZ7kY [bu`lqx'$w#|.ܨ1!5= -k󏾺n՗ÙBoEO"= WtJÐn[R8A4pP'5^p®ϑʞRs6X6TU! k4xPVU_x~V@x[ذe͉^̸ǐ3%ZQI.eSQNZUyNYA22Ks"Mrm+ BQZAxjIœ< ¯=#,FFR)&ɘ?%:~H#O2#sM:̙!p=HSm_Z{"fr1 wߴ; CGI.rr<)QÍ҃ :@Uq˪J :cBEcA%:)A+Ӱ:b'<I$V gP$ 13U#5J(2H$TrI&tIֺMqX4qbT0jE<&aGr2!M:  @,mJAi+$%fҡ y 8ër"tlZgtb36&F5+a¡~ nÌK3ꙦHՒ~Fxk3\} l$9 $d\\eM:EHB樤@DUv@n B ih9 6E94䟀."9VHȟVI#RZi}7w&`Xb+fJcyfk IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w9,O 05^Lf0)e:3)hj QٲM;y(xp hЇ"Yͳ$uO<s&_,lP4d(G$PPR3! 8!SD$5D~юI30(Mq<zj|c 2F2;|^gq oyϋ`3VƲw&d'La b`` :\7 tpA7ݿ u+@ FYq@\C1t8qBlg ĿO "fZ]UHl !85dnB+D>_H n;7A}#!+V̐*_FqxP@4a8}y9k4N`+#1NHK o7PZܐ CpL G6&\ڗ6$:bd"u5~Tlk7]9'|E|MD2G V8͡}זu/m@CַA:|$]ޱ(T   +xGBǸΆKaOXٽk70Mz!\!p?g@wmO7 2:/TÇwyvzs]hgXw~}wu5 i] %Lci)6p6Q@U wwk1nERեe t_Yyz1z p`RuX&8H5،gS_(zX Ø;xTjkx<iሟxdž3lhy6 eXy*m#ن)r9 I\x$)uȕQh$0i5[" `|Wh~ ׋JL`G{ Q@o ~x6ǹ6|_e (n91]e &}CȜw B{)|t\_)䠞)9U6[i~fƘ!ygSa ())5N$yE` rK<@ V[D jHuƛ!JwQhxĦOVGd80nSJL;g0|@s@xnHK*Ma`p U* D|PlpUpzrpuJV{ZO٘R &bP՗Цs"N 1ǠH:Ƨ o*P Muzf#6XV$GP8pG @ UPPIV}dR0x@ >kՍxn0Rc^륫AdoޠfA ׯ⠯G&j$ Fc$ Fu;e+5+g Qb aەe`@n($k(;v#Q Qd 9{G[qec|f{~7۲ /;++ J7{~IFha~ w7ɚ:I$Bny@NI.Ny;L0 4B{L `/p DcADM֔);M{L'! ,! ,"X H*\ȰÇ iH|(D/j*6jRuś5Nj1ֈn Đ#KLԺN>n{yϠo3k.2z s_*%4ꎶs6j'{mZxpݳw6 'rǛNߕk=qǡ7yyŘˏ=x[S˟U}קw{yi{MvBgg8w%sv)އu'$w(z{x Xm`f.G5Jk a6hg$zN")[cEHr !}<Ɨ&ihƄ#}-B$`דaHNi$bHYXx6n\g؂yp! _ffiߎ]蛄`ov"iurw_u9 HUj4zo?J_q6+ըmgq:CI&™* vy ޤIJe֚rトyj+]9YhRZ*4024  +Jk(i k qj%quiCY샃iCchy AIe7ѥ^dKc3V,Yt%jCô֒i Nxyca.qꮎ3i+[䉰$p.|\iD-ma*u,[ɵQ^īj'|hza@f4aV n#IG-VjjoѪS4'܄ncUZ `Ih|98QrB[,Nޝ,לҭ1bi!K׎A̭ ]FC\$c5M#Ȅb0$&e+1g=•ˉ[];_iL 놇gks=n\Wě2.{`L2hN6pL:xγ>ɷK]*47BNdUH(q ̗(5AsO ItACSo0>"#!EMRGȤt&_TP/Ffk:8!WC19M9+H}U$L@z}p'F-Ü;u{aHK${# &}w["neE`m$ YI-aޣƼ=8#>ochEm:͉̂EdW ,q矵v"y*y>@ XckEo\pgqu2>AQ7R&xC$sBtCROtGŬo\F/H}?x@~Cܗt]&LFf1|$ jЩG%s"`Go KV6zdc j_\l@^.CpI+G J'|)}҇Fi?~e>G}>TjVQ33'VL@hV!A5w4^%{9{lk \wjpB̷5@i t}Tw Ɓ8X}#$'h֐GG4h7fzQ!qD)KQZS2Pz X=ub XFTTvak5^|rWӧ`hiX8HA$Ń6WqDe{$WHlDNk؆`V7.YL`'mRcOƀxaQit^iD"KA^C؋Łlh`ChvG\IMUN+W%V:.3-]x]3X\SmٖhhJ{P^U[Ix`Q1X#= ϨdX(J"h i} B` {÷hɎfjd(Po$'_5m=eh+gXx_A&9JEM&j\'RauxDF|H5re7u7LEbdPRJUy\정G[8@_Yv7Đf DƑp)JMO95bCQSD-IKUyEiSX0K)JAdԃ^A`oKJGB$stEN{297BYA9 Hfրq7, rwI&M5VD|@zsmcDܹʹ_Ays9{VjA"ICrz&n ڠ:Zzڡ ":$Z&zjbeALT֌(g!F\0z!h3F<:s12?**`j1_HRgzazhSI,C3+Q~kVRRC ba+Zzmٕa`"d&B'+7Ax͹*GN!pИ 7B%tBBI`*!ٜ/GQnq*Gpxr&qYw*~zrWdA')JrpqRn'A tY` v\4Z2F)k |uY}yEZ~y y3wuI&dkbVtNw|dYUTQgFF;@ ߺrںEխ IGyvE [b䤭 iXgtB:49dU֌u"B:l o(iwlzۓ%KS>T8Iy14Ɓ[[qvJn9 Yt0v*J=FˮܜѰwIm}7))˜OYq̰%c0Ml.fIߠd ڔA,~9FYQŒf}栍CQeG^&@Aƽ͍6l, ?=-4ܾm]:"?}t`CPέ]$& K4Azm-80\ᐜ5Zj2*Hp;ͫ觮x J˫F va:&Fznant %'!e&b&gQ^ pl0q4UW潪Z'GAo.A8mYhE{|z^J|; f8gT{宒ipǐW.뷱iԱaIoByouka=ڝ1;n ^ tFcJ Fn}{J̀[ u[>GwG^)m>htkH.7Oi$knD/l}Pɾl9NG8|ŗu'A~'Fi]</ Iv^f'nW?)\J1_/O4GzQYIIg5Zv0>m{1ЈZ!~۸a*{jAmx8OQpY4@MO> )/ v…M {{O~Ԃ^{ˉEN)<I ?msO܂4# ,R\7mk/g,tQH)neFF?gE+Hp;| 5"-T 4Kx0!} =XQdłt?#x1aAIK.i#G6q=)sfBvMcb"/>z|xuاcJ[5!ڠ'SkfrśW^}X`… Fl]4NZ#<=Ux($%S.*Pƒ*25!5-KPkzX{t XbEE8j3Ob# 혠|YE{@ǣ1DG$DOD,+?iA1"4|fq#(+Gg(񦣍BhWchS# a Iy˂Lf63 B!~F!q4A !S{> r't& e:2(¼F=Bs(5ħgV[o5Wr&% k>A}BIȚ]PhhBayʓ5nI+Hz؇ OY |v+*,ArcYs6X41toJUA8'ivkѥAہEV٫%Y>fIek1 db ͱ|Jwj8Sяd 9HBҐL ]td$%E y'INJR^.PH9R);JV呎Q*9J*#-S -#LȜRЖJf6dfKZ-sVDf6CM”1eLn&tf: am;i$WiNs/S]s\#(_IOH9>OZO9չMӛ"F/ 5e4K3oϟD˹RX MaT&BeR뱔7EjlEhTѐ2gHztFVj6c}H-Գ[z$(QpyԧVhZS LURK>Sڬ+5U6t%EӵT̢U[EԶVմjOZ?Ɠq=,XEVzVm_:[5/Pbr#Қ6,Y%WV4nNףtIXwje5l)߻:O6yֺcuޔF>jd3 +ukV4pz;⚶tI@r60ULFbEJs+[#hnakTRE.~([خ /RA*ޘD]_ÄvW#?k^$;Xq) ؞|?Ѽf|vXm,S2טs~36uXr9͆0Ll/SD&W{^D~ޯziN (z g3tUfrq'e=kZַuuk^׿v=lbFvlf7φv=mjWv-F vrQ nՍ|7*orιlfxzM]x%~j36ғa5}R~W; <uȉ?!. mMۖ/s׺+:}jV\4_w_~_<=2!~ktJR*5QI@Oq,+mkA ;a@O2aZ:[xջ*Ag#qeoΡ#~˖x ~S?1^z}tYM,bju"NxqL# |/בoԨ3 Y@o)G'| 5#P0B%^掉=?Է%utKድ1.9iFx&?ܫ?K1ܷX4ٰ @ >?!14T>Q@s3[?D\B?=& DڋK=65{ ;jYvy,i;q 1;;HD#&'SҳD4C3,='­9(2i0{SʯMdc0؜jy0Y*)8?@sN/<3kG˹ 33e 3 Ā9ĹlHѐ r+QR(L3HR"Fkȏ4pJ_:ɖtɗɘəɚɛɜɝɞɟʠʡ$ʢ4ʣDʤTʥTP؇jy#"$Jd3\$=0|GTY&ah|CʩxK{ˡPKda/ &ˑ#>sK,A!E|иo{ ?~͋kʈh9kŸ|o[}؇ jͣ{Nl  87q L4h$a Yi5͂`-AN7̑C;tPNД@M=c /hx; t3pN{KפUhHi! : ;o 4HR;`Qٚ} 廔ڬћRYRќ=#-aN{ Ҳ#'%wКjRTMQ*a@U"\6ThR;"0] HM*Mӷ\Q<&Mhaӊ!Rly;{C i =i[ïPYZR э8:5E̳cY+ ",[Lة [p[MZc[%ѻ-[ 3XḾJ3 x H+{V~LI%% t[˝&3Zީ ܄@݈P @WL }< -Z9 -Lv5% I= t^ VE\WMCYUw^,x  0\IYLژ [] YZ2%0.`捋_PG tՓ7=y {ۄ\ǽ[  ۙ^ma-_-2de?a9\*U&8ޑCA>={Cp Py8kb(6 TYW ybF&N8˻ 㜰cX;X)C ;MXIC<AȚTF6%Vhm܀)0ſ 0]j HV]d WMVKgmVySb!?EQ.D9[RyS櫌v,QrQϠR0-@igXF,÷ k>tlj۩pQё'voÛ!| 9hG+ؑ2/v`,`>o(ssx pкi hx 뎡^Ij𰆗rFɞ!+#4HD`B-,LhH2SQ㰶k>5V5{m8ɽ6FVfvdžȖɦʶmeK1DfmdM=Mn&v >\n\Yh.n!Vn^؃ ^&6FVfvʊ"$?#bz}0J5%K8Kax{a mx0 Wazpyi/)7 :8vlH+'wpSxwXxq .C% ' )Ts*ixs0mІ.m'r?!qZ i0!/ ?' mPq;{Їwp}xsmGtK`{{tRua?I'gtPrHzQVCoF V{v/7_Fs Kxvz|qgo'%=Y*:g"Wtgwp gyy*^:z(}w}0}x}XX~(ǁ{ '=7 #|BA| Hg կ=+Xxk!},ѨCK0𕶄6bQygP!#/hPB|cslqb6h`Ҍ$:}B?mxΒRK5U5xƤ3gFm-ܸrҭk.޼z/j=i.nANm/֋# R9%mcUش%c!=^BڐFnKZDa& D}8]_ֈ_C׳o=ӯI:O51mJEzqF==?+E-ț44dCCJ WtQ # [TKD" SFmaJ1oqS9C56+]ӏiFmI9%UZy%YjY~!`O&50 Z&E\Ґɦ6)]pΛ%49V\E6 )p4Xl&>XLZiQIÞ}&% *H;2C;zi(fTuTу:,J;}YE4CNن9 =E fry##b䚻HoT?Ҹb=˞kKCZK%%:f05TC,VwQFFR>CjVC8L-i)A =4E}4ISym\*ݖyOK=5U[}5Yůp6v=6e}6m}k ʞo!;7y7}7 >8~8+8;8K>9[~9k9{9衋>:饛~:ꩫ:뭻ψ,30u͊:サ5457y8%@sfѯp2I"\ʍ%՛o%0;^np[mKaA}CyW4A10@Wfa4tqP -`p.LcB!KY Ph^p6+}{HhFCf%E?ШF@"-?Q B<$Å> YD Rr.-8 q`{Yԡ`ya'RBS2tlC}d<T6VHJiR@# 0I.D jPc(\!@ V> I*[r*e] xP$<&ِ!ݰ&#ͱaC:qls0RpxCE TßP)f'Ɗ<5wXc>!F4E=`TVC%j!\-@8"MwZ)?MpD:Nxb˥  }< 2 Ud@OTCnB7Ml_pH!PMt"ЁZuQF7Rby(!6Ϡo1@< pA}3G:bC@^REr0%dƄy_0 `Bgb8PqjI2rlv l<˹(xlP%# {GC( @@iC\A )\sXl~rb9h%HmC އ[6kNC@/ېlP98nw ܀2 uU|=ANc((. (lhm>K! Q(!ꨃJ@nK::-6?A13]*2 Ʌ1M|E}Q-` 1T9 &j qtTΉ lktc4u{ocXQah 㝶@(Iܱ eG#0"vȸcml;xDZ [1$Ⱥ!E| vFIOXX M{8)e WG!=YX0N!Ys*&}blH7 6F?AKE1bEA5",/Q^@DXM  uL8I[%E"x6h<5>d>^\aYqPE^\ `Sc-_`nZ[[}ɐ t x  "CɁ_%bYZC_ *t("`^bC  dEae"Н\l7PC rq{d)$.m{ک"0^ aD.6n#9NԀYC",ja9C,O T)&E+D C 9\E$BYNC?C#9u B*d bT 2?CxݡH8?c,2" c7#xJ c% CUZ lȑLZD%vU—a("Sj\6$6Cc_Bt\-EBZO0QC#QZ-1{bC妽F8Y T6!uH`CbDģNPC8)|Bv+ $O?Ta A.b4hv"#4Z]Z~rC ?n&`՝-\C:<g0?'T*%I#_ |0@ 8Aׄ +8 R-Py 9? R>o>P CChXAfhPĽlއ^e2W fS>нhRьu(Y䨼h@|ϗ |?tEVѢ6*v6BZeٖA'ܝQvbZ!zYy)|42F_/oQQ!Qi?>Dgʱ#1G)%E'+E..#2FЁ=l D C0m,_2&c1O>DLEؔ(k(98Ab+p?x|6;C,dB?jxs?y|@7t8B5hAN:;&)>tHut=;Q3C\4$l6oC(5=3|e47B8CTP(ȃ=tFY7THI7B6a,\ ;iC[[[ǵ^55H\_sa``EJ%I:إ34GC`Y E[ 46'ck600l˶о4( @ÖCnvo'Dh0|CWe`VAWu9ElcmӏjVA=3)F[t@Q ,KKM@ 4?Nx7Zv8^DXTwwwr/wswH9[9\xR7hni_i?[⾵3CB.MC(}ס ߗIL50(1HۣClC(4CL/`yzoޅr'@2[@5fxC@4D?puzG2ZR:7FXdzC? \13vW'tC'I[C5lEY5;CfeTn#0-cD_'y~7yfa؋|y]j7mk70ssϼh#3oh0$7рe&!*jxԷѿZ}¿ E/+4!7@7`*&8pa El0q'͜o=ҵgYy"H cl⃛]{%xZÈmѧQVukׯaǖ=vj4fƽ %,6{&& Ҩ5fwRhvWu iXmGr<-CS8?8DF&6ʵ@J4j@-衈hdHo D20h p4sPBpEnĎ9XDr o++AH o5 43h'kj (o6’ -,/n̾<3Mc3(CMTEmt&b7 > 4[JR>$ ht"*b%TB #cK# 8hfaFX1Ei%+ "P"O+dYvZg yvFX WH E`n&&ܪ 4ԱV59+vDfP6L@1 IƊa>9lo Z衉.hѾMI nqӊS@W-Tre Ljp!7D扁7\Y)RHQlWHr+Hney.Y ySrƊ6⟵E"X4Ij6$8n`Do鵂r1ל* *QG{uqn)E&)WȹNQ  cfD$:1DC71 78 B-C@Z8 _h"L? V 6:5 m *0PT n82AAJ S/KBۘJ7 )z۔SLn@ T8,ܐtF B1)VRW)/&Q(c&yHHRt I$7t()atcAhƁL[gy&G,$'paXH*0 8$)G^"9Qk2Pr# R#ȣRnd4\ B&BC2C(7@w?NB3:b9hB352C-UMiC1^j>MhH:xi#5F:5; U7G:m|dW=Typ hLhK%SV#԰̪^WyJUtKc ڰWqC86^!:Wq_kƲyŊULxu߰Ǫi2lp}H5ia-ciA+BF a%7ˎBAam3A1fϣ]قJL#ysA zLZ#PL.`ӝApp)55Ƥ!]It h}[$tLmiM8dĉ%&}ط~5eM\zoEB"6==)Mq۳ۮ=(a9lLP=j} }> whʩ^u nG Džfp$?%┟i;f39# yw~axǩZѯ8Gp;RHq@ayG${8=nH_7;" gT/an<+a^yaa4vz/xP qP>Çg᣹ycmi;rV2 ;z k\ah z_c 56gH<uS<]?,qG f l}44@Og;AF /φMo4 Opl oϜ溮D*Zo#:oHPO$ oȯ* pL5`PO btLbto#( |V0 B lgpO.s+<  U#[݌ OC ) pN/Ӭa0, p?p4 QM ڭq@7 [LF w !.z@Z U0m  %nNM$Vt\ P  ]p npF;1 ѺRLŤp&!KE#cpq#pP$n-2Đ5.Q !ϢiN po Rϊ #[# 1h \ Sq$E1U"_r %UuR1PQ41J2Md6r+)~R 2 L~*o5lP2* o>P5ȰQ,U*5/,W2'g)nO/} )s'ӹ 0 i+(MqԴ-S04!@m&nΡ/Sj,nn,߭qxh:p 495Us+!7-r4W DB(w,h5Ws Z21>s ES#a Noon@< HS:->G1ӸLl !Tnx@x@U@԰LbmT '-Cw! 4ŊL/I3DBKTQRPV,tqC Q2,^2-G[-l2A͒A@tIET*$4o85tݮ %KG Lu s?vՒMԳF2NN4q$2OO.4P P5QuQQQ!5R%uR)R-R15S5uS9S=SA5TEuTITMTQ5UUuUYuUGh'ˆ^5V5 :-rU}QdW 5TZ`#> WQ4Zd5vX[`V6V @"Y5u457u5Z$Zdʵh QkWW;5Z`x8wF,U46u^I._Y##6b_u5, 6RZ\/Vh65`:^u^?lЂĂ=8!TN ]qiqP"$vv\EgWV Z(!60`%x25Bx4j v62b6$;jVpagegQVvh j@LaP$Na7Ε=6%n?p$DnLvvfú6j%j=]S4F7sh9wnOe=d4u6vmo8si7̈d>rAJY\$9#fy 7 $wn`0$v!.wlN` 4wA" "vRnv{w"yi`y#Z!%yGx;.!w `7#Yx H8a~E$(xI&ncrediwXgIP8` Mv&vGXewu]xd]atYSN6p7`@[ `!V@t]xvC_̚X7"x7icz!:`voopg# Hac [R x^xp B@6AnC6'FVBEjgU@yEVx؄XsK9d[vEy19YXwu$6ُMt "s\;PFV`L8:y}mW4~jp`77#7Zҁ4%cbҡ^XzSywUQsYyW?y=LUNS7eUvQ76_p9~KgY% &RU(9XvgY$, DdY7^_#tY_£WbpcbaD?e ƘzxQor ig)`qa8OsGcڕ,sMJA5Ai_ 9$hFd ?PڤCRڟIf( [>;E R[FFp@C p}>ngcSX!`7^8ڡi:a9i-i0 9^ x6Bcɱm+R@>D&Xv'9r^8zvE_`Wb7 ;x Ε 9YY"r:3Cy/寡[á;Z9d[m9bxï}p盭w.'s`sj@qqs">@$n FD4wdb6T`X BErls\esUy\:~qx  8sk 7 }?h=o ^Uݣ?$i7n7}EڗYǻ&X,S|Ew )rajڒѺtCMGUdC>vS9~oQhT*MTa N5[cQ>p8Y#nM’$u[mq>u~y}>~艾>~陾DAbhA&䟾Jn4OW( gmHW+ uQ5 XcaaE# rb*[u&Ubŝ`A ^6^`'b+5>[FǓVFUCXJ _65&v&H߆zP6&v%>4B_!M"oIo)xp#8Ō;~ 9ɔ+[YtqlDp3BY ntQwbt`nv.&LmB j5:̵ xYX7I$eP\T b)LI)15EA l'ńӃb`0#xO+NŹXSXd:`$T~.6A :ɉ<TrAe!1~ ,!3 hn9Դ&6y'NQ"ԔA$(G\ImRԥ0JfQm\Wꭖp5q8~+ GƒB'IюHsP~㨄9ɒx! [ ٔbٓI2'gڰPo׷gM1uX9=xxyBx *Kguj؈Aǔr)tt(`l8~Y=ahsNjt盔hyvxGgsyF0og0yu7ܙpy}Jx{6 MBǜIA{<`mu~h2&I6 4a7ٔXsy~{I׹i|LTi :E)5ywQ‰FU98Llq.W"(9v#j5rovpJqF GV(>qiǣ [hq_J@t$'?*j%"vFɦjq`צ&mG"gR!s98P࡫VK*9ɔ5aJZ&dL3e1UJ꫿ *JjNJɪʬ *Jj׊٪:ech=L ޺p v1a vs D̀ ۸@AP ʯj[s1ސâhWH  Z֯{_K =b & $01UۖSQ 8{s7=@8w0`8y N4P@E᭖h,+'0Kz 2K볉Tww˖}[y{%;Ky s #[ NP+i[W;y0 (0: a d["`.P0 p+u+q pFk  Q`6лۼy): @ ;R `ЛIKs[k 060 ;4@HDB8=@0b` 0w #N v1zYZ4Fa5mwnۛ6O-n/*hΒwBP k>EPжP 4!h 5n:^@^0жs+hyz o@ƕ|; >5Z@0Q7@| F`<@\pȉLy}@f 7PA AЋɹ`ʨʬ\nn@n|6˩{ Tnn Ln` AR 1 V@A `)8|WakZ/Jx?Jn8|3h6 @s@4p z  & ] PF0s0ɻwxE_ Q F@w|` ;]`L \{Ԇ<>:C;n|EU>ZMXMnüMMOݷ6f֝ nl0 ,,"T y lXDZ[,nyw = @6ĈΐF4_4P wX7 `^{nq`|&0Z*A 2 H 4Q{7 q Qܽw ޑЩםfa ;kfƽnݩ]ShMjMv˼" ~K@o7 Έ-h  0wM{{n ُ IHk_ s Y Y}YE `|^LÝE>7$ ]ъx :݋ ݚL,:]Pkvy˪q>烰Ԅ PAᅆ~ zN:PɌڿn2 09D!nk!|G AٍPCP͹t38-dnJmN޶d>`YZoFmc.\q;/v"mo.kmkk~^R沜߶g N^y= Sr@$n(Y !iGNwls5CڃnDzWLV U^LEAJnP%mn. Q,:cL9Qn_JW] A .ߝn0QI fk .؋= gf\:jÃFVĜЂXpɠP >Tl4@Թƶo,E+ƝuL>j߬|5LɃ>:J`w ]n 6h`߿*h 85Eu f"#ƅ /&tH&;cHH< A64S%ʍAh|9Ɇ=Hy0${:&h:k@NF 9&5h<˓r/)]H/FťTiU2N^uٵowŏ'_yկN{@:R4 ABKC%s>LȨ8{0BxLQipAP q.Q‹he #9dG3ʢ쟴 "+/ 4򨧞qH,Gpѡɠ|&2B%6jVVr5\tUw]vAWR<&E y_l)o2SjI1/JEp&"@)ĴU]CydK6=Se1>sfa\uM9eݿ7m^7SzݱwGb]JS/vŋ7s?zu_E>Zf-0Qw JaEOڋBB*{( &~dXjG`k gfHj YG@mS{A(|ޛ [r+V^=8ʈpM b+foj xF+NH;g>12T <[o#5/3)Lb#D:f/ܞ?Ў_dd'3rPzq)"%JPҐQLM(Ipl K7HyizSg3̽E>ΰN!C)KL|91(Oсxjd]zKez2(@y9nӠ;$B OdJQ2=Q"*RmZL %Ո1}*jSi[UK6ujwKӊ:Te<^ΐGIOӲ굥g`פ*}ZX. +ߨﮈ$5y2YWzUc+s F*]m:te?T'? }5J-UM]]iG΅֤!#Q}pk7έ,W-[*DQs-/g`+ OveSUح"p8k)fqGvbX3qha\cX}c . ! ,! ,! ,! ,! ,,kH)\ȰÇ#JHŋ3jȱǏ CIɓ \˗0cʜI͛8sdɳΟ@ Jѣ!{*mӧPJJtՂTjʵ#g- W˶[CY.FU{.E X੿&>ZC jcVhQ#MrE:R7>C'5E'Cٴmoź8h S$gC|X5:C 1gN,kD[w5Ds 'EVcPx/՜E8b/Ŝ5@Cč(kayKeآƭn8C+?y3!aE0#^C4~ YRa%-maȆx1}{c\< Ҳ.@J`) jA4| bz7edCjkL#<9GAh7t7Ҹ3x#i-|Cբ@\jY : bƳmʛ|'"OzG=:ēo YH#k-k!0:z9 ]?9o恟B M1ycBIn3EC! SJ+F2+5dFr2ĘLhv$Q"d::$M)p*M2P:)'91noA>~hf3_ ,blT"Oë^6J6"B{$ 3` jULLrNFPm]f;مJ68@=e!* h> dũ:F[2әYVj6DSpOnN›sN7+Бҥ}HMX* T"pLA^R=-DR{B +D 6id *׻#iCvכeH 0ղ BJ u@D ,Bwa!oZ#J56u,&*oqH'?qYL"'2 !W) QnȜIg3z!?0!կy!nMP,E6]c1u2kq'V1H!^b%$`ZXAI Gg׊*eg=+ qv&sz8 cuqZ87ƍt!VHa j/Ά@@@I1WvyV5p%2BnqI{ A? %05)9%PO.A*_f!@k ,5 I-R ˁrq[۫zEJ}9>|-Pp388+}l)G&*`Kp% &.!tl*(,iOVȴ,8o+2@8ys!Gw ! 7,V,qxx=T .qcYA7?xc?7='hỏVjO 1fr݊HbZk232թ_4̡Gȗ<7(o(5'kG/}<){ z\FV]fс@ uzA+l&0!L o E,тi@3D|aEJL8GMR8TOUZZ! ,! ,! ,! ,! ,! ,! ,! ,! iH*\ȰÇ#JH`@! ,!  H*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,! ,! iH*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! ,! ,!  H*\ȰÇ#JH`@! ,! ,! ,! ,! ,! ,! , 8 *\ȰÇ#JHŋ3jȱǏ CIɓ(#"\˗0cʜI͛8s\Pϟ@ Jѣ$y&DʴӧPJSAXjʵWVi|KٳhM1>A(Wf]xGڦ8n`` N,p>|өq9.622Ec97/tIaTuGYoà qx6ݷg0̝.@.,؍{v5.fucoh4,a=O(<즐0(䎆"j}7؃cD9c9Д8fQbY?NXaf5=P77~@5`$i}HRI"T9f)Ж@9PMh 3˚'xFi͗EДrv (x;k9* Q * t hS3˜t\ @)dΩ9;B:AІA5T-Cc@|P}lN ;aGDcM|Vʱ? CS,LϿﴳ|P}[=nl0#\3(зd [{!@\۳꛳@Z0ש.04ônS1XG$ckJ=Ͷ{hL4n3 B5+, ,Cz7@?r3ndY(9T7.ifoù@ Yd?ʹq:;[tz'O?Yǖy7<-F[#J2P72twʭ=)C7}C?܋ܮI"BP.v[6 {`CjP=>C}/(D8ʃr@_5NR bB)aR#8*,aUT(Tyj'lHxmL`ifX? 6"RJU$~Q?-_72nr"΢v[gOt1#A |GJjWn!cSHJR8(d Y7} $4-{|6 #9@!c97-V C4!m%kGc&)Qc^I&i ћ=[𪁥>Xc`-ʑ)t#ɹqi=bH{Je8]A8gwNHM^6}=%R' !rLB# 5I |@(hq+/ʉL6G!$X=kNL):ڔNjeh!G]vUXnMLo)ʴϲCQň6e#n%ՒI)[V , z 2tA  u*s!ٚ9w jq_ X.5uHkٽmֵe5}县kmNmdeHb{;>gYoG&쩕gmՄ#{gJߕ6XXt9e0gK$?A6ЈKcKY,J[*I#c ],GF 5WV:(~gMZָεw^MbNf;ЎyaXm\%"+8@6$IЍՈ74&+T()`jx-[!-8 qܠ݆"$9Eq 8wd h Xa/'@O B%2nDV<=?#F%6ԍyy7| Dp1hn#zuKV<mI҇G}Sճ^# b.{C;Eo lЁDP "|//X^$>Ii:LyA{շ8>Dt" az۰ 1u w 5Ѐ~ {C Z4G `En@ 5 @ D @ j2Ё ~G~~t~E~'H7g~G8whx0(h_QwT8 bAp+W`}4`p0 npBr ʰ%v'&pQhE6RhX#  sg{R%`щ0x؈o G(iA np Q=P p  Xyav5Bㄓz}~ȉ߳ ((H8XȊxo(#5r7@++0ܷm4}5E@'YzWz7~ IgXF{`[g{|&[W@1YB4y@ y`qp`hy&yp "Yԑ HV5) ђ/I" =in:ٖ `$'(G] {AW_i{msNz]rQ{ 8 N7tO|98uTY(@ A@ iNɌ'P鑒)*-i 9隧 y0ytމ5p (ׇ}ٙ\xQ8.9u0(uhW3~5@Buc7Y` nЛ :x H,ڢ䀍 g{]ZHvؙ @n!F6`) Q2#1) ぉ5ٜ!`ycY0Uar瘟Z[w ~G}`3Ք  @x yx* 2J'beiæn 1*9I}Zxz{Z3OZ4@wp'z`0'xG3~Xt9DVz> &dm$9 0ݚʦzׁ< 8a 8D@@𮊪 =Pd ̈nAh^I~r;GZuں Js*ʱ@౫4` bar@s5`%0s! ~G'jtiqtGsKǦy*#;ta=7uOG{ઙXrVwj qwyy Sp 0@W 1 P ਖ7))t:'JG{yLۘzPsO{ ׵X˦GU+k%+iPڈUgo'tbga(BB& ";AvI)>F"-ۼHHav70m vz6 Y.:ዒ;D@ѽۿ<\| <\ `҆le ` v pp@ a簐4 ;76.R2<A7 O4L' V (|cu:!$ 4,0ŎC%?| Q*Ip=Xga-H, J8 5|dpĬ6]pV\0–@pjB8p` +W`4`4@=6pt" p <ʵq `PĬ&lɌ bQL¼=̓||۳ QBɆm5@˶r1 @q,I̿L@p<,%LLɖÁ<)Ϸ<QLҿ\,,5RMiLL:AQM}aj!MH~͖h>-ļ=–r++GbjE5DmQaN{8pơĜdn*-aJ)Ĩ=@¬@ N 鯐a-N8jR8^릍e.c> ^lk +,.v~͊4=*'n~MΔrtlEΙ>0o,КnU-##16>亭CLnyNO ,B>N&) Q{]nxN>Up=0j,~./ `+W\%4‰f@, \~}Œ;P_=i՞ 7Ɏ\#YAj}-CNzV/P̞8Q8}~4ϳ._訑cBY'ϠCY8Ua qh#9P5D_Q#k؅tJȸD(N?v\ȕZj_W`!xVcu+<ѤU4f6Qۊ5szpōG\r͝?7 sn7w3D/baݵܰYȌݣU"yz&h ï,` +* dF>kOsӦ;!>{.m DF(+UϨҸI{JLWRBB$ JM܏GK{A׸lm@m|) 4*Jބ3N9礳N;By:}9}8b8 h 9(`>H8HeԽ?1tRs ӱ17lb矍C5#A2ddZ8n\yRQ+A3-6* 387iȀ@j!R@npBp,N@Sfxe'tD6ЕnKNgKd&tx.ieҁg"y4ߦ;a #u] ${=m%t/,]wDx^e4ܻ<#HjHٙ#QL!RM" rj&W@}Ȅ7!/,2\$E 鶷:1! 7\@l6 $ M@Ac$Xr8ڈ2+O"Hnj0+\16n:F@ zРHP,+<2JbXS9L }p&4gh<Fvx 2pQJ%jM"0 pEGYIZInA;DFb,v頁%2CF9cA2mF2y(b7$@DʨV hGa</DhP|8.(ϙྎI2(5hZc@Dfc>ORY- + s\NA (: 1M|b KHLؤJqrĠ 9hpx y%NGjeTnلpc枺 U%7q. `CMkZ5ƮxkX >eA,.$il=~P{R ԏyf9xۜp[3R.xy$$k+q`j)cnLcX"Eací=nE4a3+P6B F uݹ5&]~SX)E8Tf؂cܘD~W `k(ہ%ȃq+6v=lk`5pեf@[ cO5v,9Ih%1hCitфZOI3G v^c934\\cCP AR @0>X"m̈́kJV*Z΢yƱcjJM֊%.=р+7ÙYiI۴W)"~#*PF8(gm؏*&Gq+sݩo ܷց$pZӀx4& Zg=bY"P|ņQ1XlJ1F; q@1LG 6oD j Js<]]Vux(F*%|BpX ve lx3SqrBj`H3ȟ!o0S ($(&j 2SR4 )p9v6 !lp0gh3 #A5u#025׸5\@(23`9CFDFQCK.QpQD<<QP$81wyIZW Q0I9Bn0Z4cEJcdftghijklmnopq$r4sLuDv vtGix}X PGh|E8L HDj Y!17Y(lGʠsHǏt#ɓL469a{`t4t,HJHCItFwP ⸎: XJI7 ^4ɸIs{0`dX4Q8BJ=yˠŝtFix{>H8KXw<6 }0̄(zxиL,PLHx49@4)| 4T :x Ѝ:24K8HLL d *`M!܈NֲMd M츬5ߌK8"ч  i hC9K yzʊ@ŔPaPгx pH ( ǢM HiK͸QЍ  )蔐 Я2:͈H mQ8I ]#3ʬhRXLDD*YJ3P]udûBaWS{FЉ י|Ip$Q TKH̃&9Y+Q^' Uh5 QN#O5\p6h !(iѱx i \%vl|Ȗɦʶ&fo qr9uvy1&曓FH4EF6Ҕ?6,Pݖ05nM9Y"LyǸHm(8_`o]5OH;Ѕ]؅Dpv΁V9M32nMnNi/8ʘ9;YXhT0H9h掓{ ;)1YIlj{2 AȜ͡<;*Ar͹$6ۨPsߚ]D\|(⸟2!'P:Z !x9Գ W xpRXL`,^coL0tA'tH喈"XDoYh]t>(^D؅c@t^؅Zn{zq; !*8$203"+mxŲ6 e@_"0:$ʢ$/$.Z?S;$S@Q+XY8-%pRT 7ndR&a&|isws8]D`M7 wxRز"Z(#GF/sB'׮8iw`_o |*'7 8{p7 qK)DΉz,?)UzBCRyǹ(PǠ*@*0*>8THO{OßxoL(xU[GXo rbw +, ,Z7PUzhzzQ,K+,e1---b5.e[{.00DD*07(|`|'ǁwZP59pP`_Y@5gy@#H/H2l!Ĉ'Rh"ƌ7bdA 2 p El`XBy<^RO:xߟn q< XҶ8:OD!y9ݣmM3Ap5)hjC7(`\ aZƱFRX<1\rB#OX,?BCP1˺S13h>:{RT `A/w ٠X|S:b<˷᠌NlNOHNZW%Pm|6J{@ 7p Q?9x D?SbC)Ce :H:orvPI".v JhFj7kL p wЅe1DJ]Cg :t.v]h.Fl9 (:V-r^O'(b!4rUed0Gvf:҅ю\Ҝsz@>`E2D8B"+RƉ"W5.I@G _ ᓮ|%,cUzѓ&%0)a<&2e2|&4)iRּ&6mr&8)q<':өu|'<)yҳ'>}39Ї!πs<(Ch`sЀڰ{c!ѥaЃmfFBst#-^! t-mI9 ꃢ E)Ja 4P?h>pK 9\Up -&jD*:: X=ܗTKt4RqʠA=!ʅ@U* WBzD`miB-sP! ]TDveHqsicIX*9$K縇>Ae@[u hKť(p!\,iJ\皶}T)mֶm X7-G=*fR:}nA]x?{XΤϴnka5n>xׅ3y*׼T!-ԐVІJPjC PC\q ੋawIn_<A apu C-b"sa@J0Cy!_&4jyMaF<z&ZnpQ`mq=h#8q Hyə~H Bi#(4fҀT;h_X50+ $5ͬmsKK>䩾Q=]:ٵeK: $jlotL5`/4V"FiͭijU^^wC]o](y~G$jiw3 ĭ85/^R&v-c9 o v >W%n7]g=H~:ҧ; ni]bZok"vd.mF-fof2j +EǛ {4Tmm@PsW쿖E^H89D@ʳ6ym[ٶ/9ñN˧Vws/{}67Yو|)|C.C3qWz/B>#tG&c%}|!Aʽ\\g[55;BDtف Cp=B8 ]eZ`E]]r]~!e]f]FIbENSaVt2U%lXqDi?P}1݅ l]2Cl6 %Sk9C rW vݴVTյґ_!*Clx!Q}iD!ƹSF!0}`ޚ܅@R5ZAt]RU͚!B"aᾝ[b'!.q` ."Ncb"cC6xš ß862B@4R#AXc-*=.>BQm50N^tX0RZK#D2<2RBHu 'f[=^qEIfni֋cb5GM a]0ʎ4{Cd9am]N#wPCB`1kKޔ=[޴yO~#Cx"wEVFܢJvLJDx)xcSxIV1H#}aW]&y%EQb%CݔGcN)F“1U#]TAX&YDfFhVSiigk&, a/fSF¦n&oo&pp'qq'r&r.'s6s>'tFtN'uVu^'vfvn'wvE3!ĄSxzF,$p@Q&A@^CO,\5z8DPLgzI,bāH }gND\80f~6.h0zZv,D  TH ZCg}6DRD5(G}ꨅbDÅhFdI&\Ch&Tr"Ӌf|f@$BhA@8C|hX̭ XXlOXŌX`Ol"lXP44ECQOO\XRXH*ÞЀy4LÑr Ŭ--G7  υ} 98M&O.(p*`86HnX.h[AC*:ꏎb*(+x"+DH+>+ɭz\@@hKJ'C_ClL`^ER(!e^B:(l@<}?,B\XH?Ǭ+ l,"BTG7(hOPGU,`l 6X$ bE0@ LU &+N8 1 -}ߵJ(ej,֎bD@^zNr^m@C kA@{i@1 ɶIA@Č̤LrŅ̓l"".ة2hnlLn<04؂mʅB'AQ&m.8P 9hB l˝:+nن*6N)^/abަ@@ @AJv/A+kvE2S0UDPUCI<`g o|n)k1>k'tTu ȑ=A>A{{1!$|Jzm3r 7Wv76>sD?mC@1C [ UhXCF mұ0 FX=Lip #(1/o(Ņ2iڌ9s! [בQi$U ta͛8sR+a~} Ulٰ(ŪLk6'ڳmM;n]wջo_<2<+&x,aAS`@yVJ$4WF+Wi?O-c֊WjJS 9:k=IQyبUϽV8%kwW0n?ymxjɃ/}{Ǘ??/A@L/#SHZPiVZ:领A0hAp• <0[HN>D%jqC!l~ E~9%"Ho+ɯ+R-<,n(@ǔkƒpG-4| B)p):u#M(POJӎ.385 :EѼrEPm>dt`D iVKc=]-.Z '2 bהle]u$Ņ`fN!ih Bw.(<|On koLA@zaSP1jEi' G\qAa 3$ڧ%i0A(]hsT:O[s=t~^t5PD;^驯>0 j@r_/ f.-0o/ OD" $_d`&_V'uE&FtIW֎~!34 JjܒJ%PlB`w8qE+Z-¬A+Hh4,leu!tkp )pXJT"UJeSbƥ 9hN`":ND^B (&0\]ay Մqtp`DEj-dlHD2͕ta." H0{ ߂HdL-h CeSLT v> 7Y}ԌfTXV܄aqB` @H.c췿yѫ^P +B&HQJD bdw]A>D4v#iLiPp)!yB<)J2a< 50 L'Z9,D`'  $ q]GTV A6BӦeiq&S>w!;xPy*e 1ݙ$X!U /[BvY,ƦX㗶=>d >v2c*L z@ JՄ}aNl;-$u*ut:Ǐ8$cσ#7(zie-qKIw GJ*А` <R`0.%C.|R\C2xӢM 8VA6Q,w],_oAv04@j=f.Z\< ,` OQ5@|'5_58FJ@. os>! H P|w *ΓbڎF5:`AFN x@6 t;CP v% "Aha ̨S 1ODzTNHB|`],ndxBD:D+dp!*BG5P%pD>o̾ȣ *:]aDpv! 8aPr,( ; ēG`;pm3:  n .͜+V,ۑ@Ăeb e--ad %dsBϣ=!#SIbL58$Sf: 9Jr da pHA )\M#@r#`'B +Uu0U)Qfpzpm2*! <XH6ᐾ`6 AOqK1'+5D#tr@rTS´^jPJ΀&PaPPJs|auڂR+tHMAz "QB"Ų Sp $b6feLe,6afbبd%%_⾜.mbu=r=KV=VK6hdwDeCg|.kax6nj(Vi^fm۶.Pyh>jgjg!}p6mz"qpppWwqq!7r%wr)r-r17s5ws9s=sA7tEws|rhzm~(h_ oGvWvNvzVytvbvgmLjxQDa폋DsVU~c'zWx6rK`u`SD mJ8hR%\ lIhC_bzH׊w n/qw}8 vnv`N/s@q{}go!/{b7,RjrdW<%n.Qzm  iO%X˵R"ꨒj5a}} b "bNRmZ .` `/L@{/׎y)5iv䑏G vlԒjcC"y㍋sy!y)[;Urc7R#-O3C!"RP键x|0gNJ/}ՆYl89ٝdNck6eFgIĦ,ҹ53};MP$x:3:&nSX`.mgX%1%#Wo.{KT$ͣ /= p!|i"ā7 m:){i[<>y/y劎OOc o5ßZ1E=R@/'1JobFϮ0iZ1)ڲq#ٝ[I۱9ۘcV cd-{`ZY٩CaX>zX0DY[PQ1gŶjҾ" o!^%!I8JE;Dy#3Ea7.*՛5yNZy ;Gqg\Amj8}p%t`[&;l0ŏ_1 X"#2Q2BaS]QxXKy;֒[\2{{c+EDOS1\Xt99i FzFFstG{ TH SI!qȔ۹W)sܺ&wAVIZuݳe:5N[S@/YUL U5'Qo$Ƶ\3 ]4S^_= B_Y^^gw]TչJ8S\9tU}G>QZ{/ቹUwuwhS6YVyU:sx^cO<%n%'=Ĝ>蹛饾[_؆k#n!QewhKkk%ji=llJF`eX;6j^|^9ojgi-/?59=A?EIMQ?UY]a?eimaXHwayAA#:sQuK;!褭 _x7aRb_ќ+۫Ԃ߀7IbRC`A.|X !ZPƉ5f܈bǐ@Rǔ%YؑbH#=p&Ǜ\uz]Oy:ԲK83z:߾wۋ3~[H`et՗USQ]U7eE(|WQX{y޵݇*.x!2c:8}ihԇME55Y, x'zx9E"':!Nx.~Ec25eBMtF'tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ x|2A#'>8s.븸7=\n,5@ dj=}N:OKv7RX,9D>?OyAm[:"ey%~Z~,# C=C?5t9|타v h@u.9xFȣp>hn 1G46k`", r & g$9*hp!5F !B{| %h Z<30`/X$.d#hĝ(9}65,kK? " !x@p$Ԣ ҈,6[H@%ү.!#bELb"툏 o,MP^2 ߦW I-ea  >p=AaG#Y FLv1:}"Aj^- kL"1 1iF de?Onh2|bplD!$,$| h4i46 7g3d:KxЄUI(E7f4!D4IMf´eW "UI zɖ5YPp}lm[yI欑HaaA1"HLL['V4?BH rњŖdYV*g aLMoﻎl B[v\Oǂ)k Ufڒ:5u+{}]m߻>fu. W {dɫօIqwe_ lZCnCh0LXD\gΩ"# }DƍhlVv4@w Bbչos5)Li𻧰6#raȘɲp \u9V0ErT͠Fم!-R*D>+nv_d^˫[qu;[ua=N5WzF2j!-m/-' TǰϾ Fg~ES 9gY9_׵HMѣ}Et %q1;EF=Q %W$D=v!@@:+4&drr0q}}$Bu}'}7D8LX%=yRG^mMdmh{1!4FthtQ} 1 |b9d5I `Q#$`N0Ai'a@@fA@kqh@mejGr;95 ]RVH%9ccxftf.JX0Dg:j"$N#Χxqe hFmh:8AtXVEHcdxhhxEDhȘVm]&8`(XbNI[5!Ad!+Tgn+H3?2rPro);i|'#  )Ii9h٠7) I%97"ɑ&+6(ّ, 1 6.2iq3;2$aCp?6# Bi5=ic5H9JY)bWc!L%R&B$(#;V=)SB''d9!(?C>Bg>f)ky498#&U#YA&tI*11begB,ٖb2@)URA21 d4R0 KQCqr,2Q/r-*ytrr[.2 .R|R-Ҝ“~)8~91%R*J"Q"׹dab%)2siS%򘈂967❖B[)ѝ2}R,),&SҟƂ@ҠR;)6$I (."j&##) :r$Z% &1&+fá}Ùi,3&Dz .ɘzv2"(12.8Jir!-Yj8C$ z+Y.L".߂//j.mknz,%~ʠ[{3"ir2lJɩBɢYWA).ٜٛZaz_󨐺0":YJ|R2 0eIF< .ژJ!3ڗ.ا *Z9=Vh j694^5jz=$¯K2k  +Kk˱ !+##&&;0$3{2W(6B9fJ(yz$ga++˲M:/ r>,BK**Z4cJ;*!PK~FUōJMu@v*1Nb0?r:u22!;J&SZAPhQ|η9 OcQZX yj Rʹqpٔ!j>zj °~^Zd} t-Y\jIM8q! -&){T 95 l|Tfp#- 2-2C`l${>G+"i+,nԤT`sWMfl8I1j)zKcsR(5f;/qͪo?WC!vbMBP1*]٨J+* "&g9qɁ_c s?Fǂ)-GARI G|ʠٔ 2GLP*ZW-Ԛ EC-F7N΃FIJl2׹yI*RH:̖{`y6rs4SS> (2,R*=J:̻ Ό+D ojۦ%lv۷ZΝӟ}ƣ,ڂK~KO<%ܠlɩ |꺘Ⱥ-)ʜ4ʽ܇_@_B! ,! ,!"O H*\ȰÇ4"BaD3&Ǐ CI ǒ(S\ɲ˗0cʜIS%Gnbsş;$ЅG4gPEW92GKk6dOq~JٳhӺ -QQ"}t#]wr{7My+ᘇ v nĊ#KLצS٩XmVjRh[IiA5lGoug^wKuw*Nߖk}2cYuc˟/Mzc}I^|X` ŗfũ砀(a_} E2]]9݈$hbK(y,jX!]Hp"ڨ@>"y 9{#=u5)褅{Y҈dh.~xdiZ!˜#Wz#iV՚Yatdgu.g*Υ"֦$a)B癔Vj饊w]`Adss_G曪bǦyl&*&*zfl*&$i)1J&`Vk|wJ;+Nk+}~[ةvc:G xRBʨv-n{kj?3krb[c5$cbaJ$l$l2[q̥]"~b#]*hhT{yv*FlXt;Ǥq_ju'563i2 a[d 'ݐ]TK +N{ q'Nd2n1/VU2Ug{g/Lv.褗nN:ꬷ'Kyn/o'7G/Wogw/ny4͞tg>S(œ?heGZ~3[h3!8, 3r5P`;8b6(RX7PvbBplbL3auHWG,F7lk1,s(/.ibS$ V|0 ~W<յC! Kd«`gMm(j+c*h'ir p4$(Fjp&ŐfL3$ϚN17|Lޒ.jL%2RP,ErS`>[`1yJeȟji}DEGKGL ZyJo EzI /]ɚ&ESnucL<6MhFB\ K+ 8VQeel?f&8s5 !_\83@ x3"T8X@f,@c}%8q X) aL@/DWEK5C 5,OVuQ?}|z&84u |20?(!5`Pv/# $hs} Rkg`/Bhk qVu*HgƸX7XNbY/Itőh!}*tld/+wȂ}m\ XA~?Zm ˼[dN0Z\nQpQWB[b&UTC--:|O)Լ'n]!˾9FHEEy[۵{t1ڦjۓJ` :i+7 . >$BMGV5Rv:KIFc!.ݦ}M41 _\+= 08 ;Qp9Q'a -,4`1&?RN@V ,`  `ps = ֆ(CN;0{Nі.@ɽޡov>pEQW v'WN.NVM6~{@ܩΎr>F\ 4"P?>l~{€uE"nTӰ i8uWq}N^ұ>'(=Pz _+  H`!+O ~ M{>'=F55=0Q,}>adS*S;?q`ļИKMx|C{M/+?ѹm,.f?'Dۥ{ ȚopPAFz0'0jH̡/v"(k0jCikxHp*o BA<3iy-$pB&JI d-Լ[#2K-K/$|!3|iDSLs6N5tAsԴTP9 }A}eoo}oVcylx{-/Է9r+qァwO_- חm%χԋ\>>t7?~_o5qρ`m<[G"5t`M1Dw !g@z7TDC(hC*?,vC}H%pݲM$G=F, B.Oh %v9aD6i;h>80bģA펒;b 8H\ y9揎eL'?͑YQ(>kNqbɘHI.|A0/a(K-2I_I;}D)J ԥ:Dpu!)nU(D'6 =(7l+Zlyo guKj˩YC і\j[?m?>Dz ewE@[V6w}]j:] 8@0@ZZh "8@n``+6XG&7FȻ:PT+MOQuqqmH}Ī;(caOǟq~UzwE&el7E.`ȀySP Ĵ ܑIp vRZIxW*M@a6>%ѡ̮{6ԕ3LnVȢKͱ24'|LMCX@8@`` PKR:X9[9ϡ~Ar}b .;tb" 6,fi[6ʶ6&&$iY ]HPꔨ/R("GB%޵NED kIęb[ֺ⒓i0 `}m# w  e0~í/LH :Pį} >&o/z/>3J;vpI Z1+.הvH|B.` խEN@hoL pDt(B @@LBN}?/oߠ///=CvG.u7澲3]",`\H@Z@ DhH?G0N8Ê(9= B/50U}43'B{4F38)$8ڶ|&R (=ɦrj2"KzKx8kW{(*D 00J`B(E5íM;(軝aR 213;C)Zb&?4D1A,%AO"ӤMCx X H ]X$Ћ8 ?d"TTXw̾5Hch;Ț6py;OZDAgb:Ӛ;]= @ 2 R HR+}9 J,$#H^_8,u u5#!=CJ 4BҲ3bի э()37 M Rg*4ER`SЅSO`>xςlPS@Ұ˾#E?#_J#\J uuX;DO+d*:PDU݆1pդ+Zƴk_-DYc]K5EUeu%5EUeu&6FVfv `A E"vG-`j42EKED$ĮVƛ ˡ:\R*a?[FT>ƢMҌ2Zz8KKNVY"SZ X"Eb\b{4|!![4,FXWL& #ޙ>b}a0Sa5E6C&7j'"3?ccEa?2@cs# Jd+ޤdcN88>$>BM<<TFa4bPr}E6xr2}`!bEt\<$M\+2JJ1Y>lΗ n.^>]*pFtVufvvwxyz{rGng䪢g0&_p}8 @ ޗxKcQFYx/ad:Gv^tsh~hlώ> dA|u+N;i+L H9S ;ȩejȓpT0TL66ƍS0LFL:<@ZPpF>.0k@,2c^1i!뮤ɭ8kAAJ2$d-U̞m4^S N&0@sCKRK kx픘jF-LS pi0H hAk‹0$6`)>`h8kn13ZN?@l&cN#F۩Z6$a~B="}-^Poh]Pyr] )00ypFOS0q0@ Z@n`sTdF#\ύ_WI r{$b#Qt*>b4d=^{'R)-'RO/$H0A(ʭ'``ss@Z/tG\TF~iI;Sa"y m9.Cc.UO+)1e4^(N WJ]ITpp8j 3OsV^ws<yf/(c'ϥg&=vUf+ddAW"1L/r3fad,w==~fΞwV&p}( 烈puFN&vqdғj`q3Cvo y/^FeiMoI{cw*>7Sm ;m|N5++X:X rގ .8.'1 OqTc]dmf4\jf eʨt.=JcCT((^F0(j00- .بՀZڦFFZ<06TlL`h4 8 uS ֥0$J Tr%J)a|ʜ9kSN=l)(΢3HEw:kҲgӮm6:#NrSAV.65gw3q{O2,lзs{wއ 4UF2߮=^ӯo>_ 8 x * : J8!Zx!j!z!!8"%x")"-"18#5]O7=#sI$I*aE>XvwNL6]C4J`J9fQ&A=|as55nevhC"V5bNTw^uhxꙘkIPl饎z >{JI*A =@0;L]T5̚,IR@Ti6|K?$mnO@m N-\Tá=#gCȓn48=iX' 离@dN4ȃ5s׈>䯺=p1z >J@|<ouh(qAJLP=9k/A+*r^|r /0ohl3 8cc/I[4[Im8O/2P=7}7ABCtF׻GN]XŭmZ;zn37ߞ端*J@C5#,7+1DQss tCCŒ<>A| @7X 3=|=-[VP}G>rO^Kضxm|Q_PVwC^\GfF# QȾ$yJqA#HF"X#q]NІ| ZL:Սue$owcEq$`"H b@| }d?xa0 k.l;hP#T5hP.r@X X]>)Y 5d֨%bq%$ o3; L02$,eYvq\< lxMUIDeJUs5ȮsM~k2+nݼ75)mƅIKPI#'{`A!? Id6O,d#)Ք H4+ngLAL j(jQzS9re|'0u٧GK-Ezs33*dJ9qD,2s[]MtǙ*GU= ㈍j'D*?Kӭ@[)C#˟Bu 7qR K K}S:;=.TIy|$=h@hc#n{^+__vg(Z+Օ87PEmWa3Q0ϰw2X\.jW\fT9>ҪD7z6/}q9\39AN/ZQsp~(.ovsFя hMgLZ]QWal_XۘlκfF)Y^^[]ZF }ˎ{^&{^⇷rd:822O- /Q d5W7rϬU^Ef11ԉ/}I, oe*+tшd@c3[Eg[Rϲ9'k szEoӫ{/ԏv^SW&JĚۣwa+{4CV[wD:m0y{ulHX{>ww4aWPVDԟ9bW+ɪh)nʞ1]IN+-hɋˋwv&{c pJ=AV;y;K3 Gɫ`?z`:At²>#ظ|a޴NF7d͜wӐV’@ᷜ\Hωҹ~okKܱbU6ѭw['nJ=/.k,3G/aD`⛻E.%ƴe4p _O3A,lkف˘0?'ޱ~}x|4>ռνk֣x=WzOHr3)誇|ׯ}nJ~Cb?{c~/e چȆiXNj4s4R_D8݇Ya]m$Z`phh`P`amgL l X S$G\ MZ t  `{.anb] @!H~x |PPZ!n`!!!ơ!֡!!!  "!!""&"."#6#>bX $^"&u &%f"(BH V("*['~Y0`*",*.R+~b,".."//"00#11#2&2.#363>#4F4N#5V5^#6f6n#7v7~#88#99#::#;c/2352.;ޣ_ 7#4#>"BEA$_`P$C~FB=!FD$\0pIJLAFEF9`$* p-JGLsI*C/.$J/#H N`BETBE f4\$A)d jd}p^?Xő$Ad\E$aAP* ev% %{%Z|0#T=2fN50g4T,%Y$Dtd[j$@N@8\&*Ђ#A&*$Kp '-) B[)BU H'* wy&%N0Yd @D0B'#% 0p2dy>A& B+B&hJ}b\ybV+[pw'Agy6}o@+JLA$lh|v(}~(HQ>fHdMTO&C0Bp.ppB/,S&`%5t AP@1BHfAh5P3J(0CB~eS(UgZ)R8j\oȦXo@J[HXD#0@$tlnKG{ E0 hH |dB0zD BzBEWlJdB|BV) Llt-$A B"kDBzg%Dj *A*Aj#0(H B*2ĶF$p0cBh"D"`o`A0 &h"X&&8A*PG8B*B10 04,Y$4h)!0A5?B.()B%D, Ť!vGJpl $ @d)M@vJD 2 %?ZD[0+Jt ȪGdtB@]x'-%j?( fAB] &H>.Hn `+AܮD:.Jnd D~nnS"#4EeZJĆ \%`씂D"P#,^N2h¬B (,AB"hAT&.+JGJ @N$f%zF*K0(Kx.ܲܶ"-Ҁf*AH)A BJ ĬDRM,n}g$<-Am*A©J"#HHT  B"0 0JG@0NpO0 6cM*N1D?xdW1XffD8T  5pX 4 P?<I ΖA(|7 /m)BmBH? m؆2H/DRbW'Jh Dt.rB _Ҫr*A,hLCDrg@}K@䝮1 2d 3+C$s9k󟼰N,Ad'ȄڄB//CQ1K@U$tK, |ov)WB )5 J,%7rq 'ߴ\3A2|ow Ųop\2K03.Ӏ4vRrANZT8n4/TX ;JXpK+8)l'5:N BYu[W%z@2VCE:loA\JԀ.1A1H`'E0#M/AqH 4Vttʶ,D""#C$Sr.Ĵ^2+rat}D 4AhjWҀCRD.ES+A +N"H䥰I;zEH240~u G0A4n6?75-qXgBҭJ un}_#`@[@oP(JtMqde,I8J(\g[hJ#Ƕo%#c%Lm/M6j[^j@^@ ,o35"rWB'oO|w"+0Dd K4?7XR&DBu7**`q\y p x9҆Kt9p4z6k7 Jcnh)M`,D?0DK'4-%xf8JL\C"h,[7?ͪv´)B܄yjk8wf6@  pӊEE-orEKBdp&(;Cė3 dxJDD )tKyB;5^zU{Wnz?Dq&haE Ŀ#ghy>::?M )/kOhdCdւh@"B/#GltHp8J<fOl%?E"D.{ i5iʶ## C#8 봾K43 Y> GYQ,.w`~ a$G @ }23P5m#3܀ ƈ,Z$-tfХw¾죂Ƀ>~F pܤ ~H P/(c뷾O_?goa)?&>&?#?"??!?ʿp _?am?@8`A&TaC!F8Qa}(fԸcGA9dIiTdH s7%F4a8 }=`CHP{dz*IaZdVf nJVUoG֌9S\҅o_A X=14barZ+?e a潞B${CFA8匮 u텎En|m߿(xʝ|y L&h.fLPo//@sP p Agc/_! kH%aIv\Cst6S1=?#%@gBPDQЂ$+Ȝ5q v \R0%٧6CӃBqf5 i/!iL46vVǚբWc(FYuXe vmYY']Jwr cMX]g.iJ!ؾQWk˕5!wi y0JSXbB¢=Gh>%X%v,*3THgq\h! ^8`e4<%aJД`f=]朩R=NqVH@RO>ڠ Jv ˨}XSlǞWq4dZn[3eC˃rm]V(Vwj1O~iP~#T5'r&^"h8{pzxRY 'ɗ"?~ov5y| ^f=FŠd,~ qԬ&d`IA;PY 1w#LǷ M 'C퐇= |( r>8CL@p}f :|@3i lڈjB]A(2WԣBLG0sN 6&FYD# BhF#>ȭĦ} eQJUfd@0V ^G#RDCmcYe/X-1`ALDA@g"YgFezM+Q!:X THڷLKiU`~%$TW2GfEGdgR g VYl`eR֡;Gqmv Y+QՁj]l▙ZIWwA3p+]3x`ICX1-BV+!lkYHյ.k YZH0I <#v71(QS*eG=x>VX1i}+;(Uf5>5Sˊa 8! F`}* D,aIwT=)SFU;V@aNdn}Jd0pܐDچM*ь+m6\hgTY} F"'>'эKiI[wѓ1iM3қAjQ06GjUխfͩajYϚֵuC`}[׽s_6]c/v6gOն6a=jDԶmq;;0BVpl HR41ohL(V5DASdǝp_ef]nlbX;A𑜢 7ɫp@ xwd hA'@qttEQ荈wʀxTw1yHt(<|l $KU!?;l7.LTh2p <5 7 4 Ԡ3~1 Ϩ4!vӡNW}W7Pַu #ß?=|7[|O.*.pؠ',}Dx^ fAo@ҡ F!#ȟmaa5`HGoa!(VAa22bl/7k䞁aB`a5T J4A(`AAE\#o //  W P),5YPPO DP Hߐ.p[dDyޯ\J@̏ ԍV!ҡ@.A*c@DLPbZ` ThcB(r/`luA"߸Aj" PC BR!a 4"QR/!2q 6 DHQO1/_tA b?zq   )$OPFjnpV`٭h@O!ҡn$ Ͱ|V">' ?ΰ v1!*Ύ*x A(*'3 r  3*qN B%_2c3f&A'k`)'!(cr* *S**+1C"X$, d+IR%T"jL3!trj.oNtO.0es@nRjse뎰7{8r#`sA+ +qA,K-aa-K&O!R&WsrT\7c39 6ٳ3n37w83<.881# S8V6, Q ԯ$g,.#03 3qU5W.Ӟ. xH n,='t"3 ~9 (r:: 4,,ʑHaB E1BOBBCACI_qrDKt6_&U;XTL3M4^"$@ @#%F@QA.íQBYI&y.U!jI0QPKk.=3T"r ~AG= ! OA;)H?IoO7u)1Pi5QR3)#US_RkS%fUMؔPh>@(X.4@2N. 1`̯A !qLCV.2ܬ:]] B(E֎/guK5]yq €p@(  !bO !zȀQHU] u<Vco^./5B_u` a` gvY6"8h[+14@g >j J \-BAG?'FT|gc< NnV-!~OnsfeVW A|`!+ L PoGT~aUGo;$l_m{3זP7<Ǥno1wEhW!vB"@NdhW!.3by˧yzE"$>yy"z_FZ2|w}xwfD!P lg7 ^eb~~}fy"2LaZ_.m&A:3,6؁ b$xA6 8ׄQF8Y]$Veximq8uxy}8xϘP-,7!߮a{pAz#BT%608~%‏mAHafȱ#X x xy$V2˸x h $B/yx8Y x"#Y$V8 J !BI :Ar! "6y! a0H!,!i%4a8 vyGo#BVa@ c `2 YuٓYt$eR# R km p9" z azAo9/y#`6ٌ/99"`a !xiZٝߏhA  P(t}Z t`iapNP"%raTA rAhzps`;: Fh aFq:$P-)3Vn:yB@WZ:*h@@+ b=9%6 گ`@A|@ v:` |a"-n! AF:%yh۶q[y[ %lkY%S"|:$aTn @(TQ2 1b9jk9h@G ߯! ap[D(!f{A![.%l`V :Va9Va`||UTpYA *aU\8{@(Ak[ ˵\\a՜ͳ9i`Ω[=Yz;%XsZiq؀8ͯt"ؘF!!biO:bArOzOY\ : f^,A}[&yrB2Ef`,`T[ * !%lݛY9 r. T[ `@(| ◻a` V(y~5"~Aik7ܹAWAIE a  aAQei} l}-^A "b] &9=pnr#wPX! ?A@ ߝ{}ݝ `e^u>D*^ ̃~Oo 0䩛ާ h{AVow [[ 8k n?ey[(Vxf~ %~%> K-o9!y!^ Y-… NzT00j 1 .|R .#E9#eʖ.=vqJ_VGUYjT)" EZYea!P!!GVqԼQ *?u ԗg}wclG(<Z_׎%,ڷۦ]ܦꅲj-͜;{ :ѤK>:լ[~ ; gnNIIKFST3L)>7ssIJ #Kym5_dmtkr~H3ikԫgvJ:F͕YiP#m̈́N.ėM5rw4-\KNJQumW4yDt& TPyф^K3?g՗?M`T9I %SzDR7G䨃t(tGPQ:aGCECEQkR(EٌOH_ qMdSA"f-AZ;*jQ2:ic]o p[Y`OoFl{g=lA?ycR@Ȁ3 5zZAwh5RԓQ=x%ģ>_vYTV4ذ*̡&JK"W!s%f= 38S l2ܝ|&1_I>x$ϭ_d1GZu/ ٥wD#N48Mh{Ni?N -R½5|BcnV/1ZC jcVhQ#MrE:R7>C'5E'Cٴmoź8h S$gC|X5:C 1gN,kD[w5Ds 'EVcPx/՜E8b/Ŝ5@Cč(kayKeآƭn8C+?y3!aE0#^C4~ YRa%-maȆx1}{c\< Ҳ.@J`) jA4| bz7edCjkL#<9GAh7t7Ҹ3x#i-|Cբ@\jY : bƳmʛ|'"OzG=:ēo YH#k-k!0:z9 ]?9o恟B M1ycBIn3EC! SJ+F2+5dFr2ĘLhv$Q"d::$M)p*M2P:)'91noA>~hf3_ ,blT"Oë^6J6"B{$ 3` jULLrNFPm]f;مJ68@=e!* h> dũ:F[2әYVj6DSpOnN›sN7+Бҥ}HMX* T"pLA^R=-DR{B +D 6id *׻#iCvכeH 0ղ BJ u@D ,Bwa!oZ#J56u,&*oqH'?qYL"'2 !W) QnȜIg3z!?0!կy!nMP,E6]c1u2kq'V1H!^b%$`ZXAI Gg׊*eg=+ qv&sz8 cuqZ87ƍt!VHa j/Ά@@@I1WvyV5p%2BnqI{ A? %05)9%PO.A*_f!@k ,5 I-R ˁrq[۫zEJ}9>|-Pp388+}l)G&*`Kp% &.!tl*(,iOVȴ,8o+2@8ys!Gw ! 7,V,qxx=T .qcYA7?xc?7='hỏVjO 1fr݊HbZk232թ_4̡Gȗ<7(o(5'kG/}<){ z\FV]fс@ uzA+l&0!L o E,тi@3D|aEJL8GMR8TOUZZ! ,! ,! ,! ,! ,! ,! ,! ,! iH*\ȰÇ#JH`@! ,! ,! ,"X HP) *\ȰÇ&Hł/jȱǏ CIɓ(S\ɲ˗0cTQL4Ҵ lX2*]ʴӧPJJU>̪PPGKٳhӪ]*h@{`!!A'.{? 𛁢[rū_ 0X1cǐ%S.U5lsͻ߹)V@ ` (xHrsj.8r>>]`n=< ǞF(h p?5P^, xH@ 8 "'EǠJHjȡ H} Php@pDiH&P>'=P|Ow qe99/Ri%ZrXa TBeJixR`FQNO  ka9fj(-aՎƹ鞤jꩨڧdJYڙiɩBlR }k9~fz3g&̪u+{m(ЅU ?2 j>p-X;q0gA6+kIN2[ubݬ_ Bo7Bۄ0A)<` X 9|$l*`$SF.F87 Q*첉5a=sD=X t+, $p@ ,ddml -pmx#KU{߀.n'7G.Wngw砇.褗n騧ꬷfDnTTOVtJ.?Uk'|AeV]y5_ Vӛ.f@cE6tB`ߐU^c0ׯ8%\sIGu | m#lЁ< ><s `~ߘEhBj#H 7 @lo #Ė*=ri8|(Ũ+W* B `-sv9Ŷ2 P8T4+Z"- : X@}ZTŮ)~^=M_HՑkLHBJex N.̤&$džI_ RA&dL|&gIK"aMh/Tf3 iZW,˜a4Ć̦6eMQ2h Ir+ $s~ @JЂMBІPvDB줢 C4ZZU i??*@! eHJ•k>:qFz 7%F{K2pҁ:WRPICa!C4 IZX1UOJRF9 hJV! ZPDa?RnMb(WVNAy%miY#"QubGkV'dQKeE-榜YPhFV nLJ4RQ)FҠvTHQTzuEmd+.͆ 4)B(uVad`WcAhp1*痁Ͱn5w{0L,N _E2՗bjƥqF N`@~cLdHN&;PL*[Xβ.{` J2̾]S"ln1p5]3sGܼf8븺L-s=iM7#މzo5~! ЯM3N;]Yr.Q*ڪSOVۓF +cٙ.H,Z}6g` Bk#*Q}XBJKTWKvn'8Uي~gѰ"Zx g{G۶|[bmE]ۊ6]tX3ʥP)8 XےVwt=%Oȭܒʪx䮻IZ;D(ӝR[Ϸ5!On"cȉaY7)t&i 4^;}x+:DI2b 1{ct#QK6|k]Npxh0|c3&oA֏1w<`=f2wcW3^2oO}]G sDŽ\=g5ŷju(Vm6mHmG}m;(/sαkAUt9jYQԉ%(6? -~x(l7{xv6oyR9w炡wjvwnf7wVh*vW&(zu.&4)D4x_\B([1AjQ#/ā)@s!8iww2y؁ٌ q:ؐcƇt-z$."p&@%R\ݢqNF{j"* T3 [ %1xsDuͨqx I?v|(Risw)[H,(mH%o-8w%LjWQ"DeI`@єJHYx2]8H|;{XVi8X91tt_9[0K6j''t%5y&%F) hYoX8d^bn4B0vp^E$&!EEB(EEjI/BYԙt#7x67חUikyUsY8`7`\Ex`s`(#H0 kq /5pb(xy6/وɐuf1x%szT4"b7L39rn1'H4c`'8{]5vRJzLYS eԘ|9)889Yڧzeaڨ:ZzکVW9`@ M~q$n’!BQ]fS! 0p` x@JA@ qj rP"0Ѫ ڪ':*A ;8تj*r_ܳ>=6h a[U&TvVloC1W{Ki(4>Vb(vу7BX=ES!I8Ta@ Z :P 0A6@| wA[7(4> Q;UZ @ J@70Gk lPL{l/ 1j %` 1UVxUa_DËKVm@(-+ fkџXIWqqN :0Z +e k0ppQxɻ]0 A"RX "+_A )B41o) (rAWf~n8BQ:*K\K"<%lO$|` 1!ڳ  #]!狾sD1C3ۭԕ=>"q[qDI{pj v1Ct ++ &u, 1 AZL[[>|@H J$qIĿA$d]pIo,,wZ,,4cAs.7˼rh-<}$''g wP_;w\̴ ǻ}]kmɋ j!waʣ#r_ g1bꡕ1¶Jy=7'yRRxן,` A#|  gOp-H`1:4<M 60ZpJ m`|ӎС 7ݘrbh< C_MZ,F.PzRSS5Na7cʭ{˄B`X3Y7FK4cHj =!Q Ś U010KORjU5k ] UζW޽̀p| N@ Hj pP_GO,8Lpك_,@ d*5 阞难>^~ꨞC$C3u<8hQ5tg-Y< Ξ(a ;#C#r*yt0Hz)* 8B X@ξr^^/:){H4i]JF=>3?7#9XF5S& OC%@N> G0m ! ~: p 0p h0Ӊ  YP5`HvB'Ě;> N/ l_eH]c&qa DATo(KukpUd߉: QBP .`J$09 E  X @@Qۻ+ka//+ɤW!*RxeuYA1t_Cb4jkaUWBg]1 F0w`C_w0!#BcD >/SvD 30RH%MDRJ-]SL5mę3g OLؠAFlA`THRa?n| )PXpjQBz*RK ;?DjI!%PVk0)d)#5 bP#x׆C 4uq$#):}\pōG<9O"VѤK>Ŋ*߻%K, )+1H $TSOE5UUWEiHtK6AIN>R(ʠd -V0H2u5/4S3?MV YΈz=D۸YDJ(%BM ecv+|]$ P"ᴆݢ`5%"a5b'b/U-b[(`(nuL(ejԔ .Jcۣ M#:*[df$=V)Lb]dI {*TRNUM]-";B@G1"ѡT(V Z '(8tG't]X܇j(A,.(i4FR( m8@ >v_cJ?7?[=v!#k[DjE9C_jtcbA"v?H_u,>Z ƣvsv$5)VD!+Dp0bED2fB#auC&7( &t@5!b&Sɤ:VъWbȒ8|EL%b8G:qt(8@4%p8F%2T5HF6ґd$%9IJVҒd&5INvғe(E9JRҔDe*UJVҕe,e9KZҖe.uK^җf09LbӘDf2Lf6ӕpgVӚ %}IMpg8uM@.K4ˉ챳%)|гh48O~D=S$(I)O,2YDOVԢDYЄ> ;A m4EURqs&mFRS")CSzRԧ?59 ӘjdIa;[ڪmnu[ַ.n0\׸Enr\6׹υnt;]Vnv;vox;^׼nz] o|;Wmo~{I_׿}+]xFp 85pi` Wu ,a~ 7pWI%$KX[ع j_E-j >w {bb UqW @ x)\+g6VbT^""7 ,e:r/c8 9fqe0 HZQ9Wzs&/ (H DbP8$sgE3K# }D/ħ@.Aм泟ϡ.ŅD0!Fg`HRBD! 9@BCw=TCХ^`V\:fE|/pyx gj<Ǜ M\5cT̛ب~-enk.@>vq F$f bBy-buWݓǴgCz78{xź~CzWĽS\/{=~6>X7=ﶻ=bV.!vc ¸9/ v,3[{@/v< DM9T;e|ewG|˪/s_~Ṟ0=|Ҝʄht\Htn1Ζ"/v_yڭ|g.B9=3h(Ë7Txk2>1S< \<{>ӿ7 AS. fZ؅`k=dh:hq 4==@@@1=\A@8ӳ@#9 C̾K6#kA63ZlnS`=\{=B!CG"¥3JS@ =&D't7.;*>;ڻ@. LJ{A4C"ecA4.S3r9A?:"šĚ{10-\zn@&,B4.8 mY`[p[]p8۩ZҐ ĕU)[uHXH@8Q\=\q[iץ!A_T%f5 ? xO|uh 5&`%}HH ^]B%5EUeu_4m_SN/(N؄MЃ?XJ0`؄ /ߤj`` =x, =J ` aTy,֞J01NEHv* +^ :,bBPaЁN(J`('({c*++<6 ɚ'+]H90x3>JMB `J+ʩ̢LR䷀BaE "HI^8K-MM Kbʲ=Exd0fVGsc>\fdi?xȂB`!P4feAބf(m)I+ΪwP+*/!(@Hf0V?`Rg^zxh-{J簺(c~ >BBpaVb#XH.cK㚎2J B(E脤>BMP`V( uʪ'&"EzxivkDM귶&Yf0&8hkƖUyP塺&̶-L4,jh)6l@mҎnm rPlz)vmc\ mVn6Pj j,O>(Қ,Onijڕ)en΂o^o,Ub ުVfg&-f.ެb p6kfjg.~8]mq:pfvqO֨qZ>n pbf(\x^XNq/q O+rv+'cfh~(*q?n*_g1hr//,-,'zf)3grĺ s%Z#O,o.ϺitgqzNx~DksH/-9r2_Ftq+t WtE@ ins/墲~ NM^7o2?* [_dvdXf/^u7?unX&tZ_LV3GsorI?>J9_I;yFvzʒm{wqr|/vG⬇>xn_-nc YnwRתk?p8hZsUtgSu:wpKxefqibu+GpaGjg?/tQ~qr:pmJowW羚ezc7}^jugtbiFwxWoYw=o^|cy"^džl_|/kmίfM[`ei}9ӟ~"|^-ڿ7GWgwBO V_4 o TR]-PaГXx5\ 5PW,h „ 2l!Ĉ'Rh P 7@Q4"B[(a 48QM:ydY) 'XdʌA'Γ7,ڴjײm-ܸrҭk.޼KX@0EPQ 0C) 0Xy f͜RPȓ~ C߉; ^Cz/n8ʗ3o.X7K@1pCUjW@W84 |OxCe10Do}Wn R9L= J8!Zx!5p?"lF0$ eYAxW|-Pi v?)@$@'tx 6Ġ*$M:$Q޵aA}7bE00@bi& 蛗9 @Xe T$DI9(z(G%AV#a#jYJ٥iCovI;#@'Fe:+ %<?+ğ@z%i}63錾И̭z-ኛVTXgZBpC [m@ Ԃti֖ɀp *98;1[|^kPIefn NiEpO%*A*`$Ѥ p/Ix쩣1| 2 QK=5U[9[~9k9{9衋>:饛~:ꩫ:뭻:>; k4;R{_<+C̻#7NdIJ$=#>EO}A*ꯏZ?'@&3?W-c%poo+R:q^(U]0) `@x0V"Z a TdK2'd`8P QC$,A@2 F0]Do(=Y_ ~% F8;V?T&x'w,`P׍lfF3qҀvƳhۻ'0if/,uur,e#Rv ^bNV%Ugg2g[\SlA8sdWq+ v.- n Z;jA1E t-{$7I}w@-֦ N~>AkR6.Lū?8ueW6OM ~Kn65^ VKO:-s>&a g]X @dlP|z p 40{: 2^Dx<=SG&л('NdGs%޼󼼴WOF$OZďj+A-N^pHqˇ 1} O Y d^:g`DH7- U4y} MȄ Ou$Vsk_;Xw1؁UWR_0u1l-z^eP@DA&|Y&ЗX)lBGm% #  JN^إLxP_LMܥ a :B%ڭ L`BlЙ .B%? !!)C ,YN-"-ؕON"TE^y 'Jza=]P$A̵'B:0Ei]sUb'ޢ$H)"-"D٢Z"2bC5D&.FՔVA@E@"\b) J1#965:5E`4D p,>HI#>>j>?#@@$AA$B&B.$C6C>$DFDN$E 31UFn$)@C,rHXYUR]T:5AM#$WZh#hO>N%RNf%UMc =&,i=ޚbd-YaffBJR&d^bSaWc]%SqC"^H[fUVr}Vv%~W$Et#`&PjThn'b b.fT5l*SBgIIgYNqUa.'ѡ[#exlZ"ue)J"Bbmдe8Vt\kI~ese`i2'q- ќTI"uՀ܁N(g&'y>y~P.h{2[¥|#Z&\gVh.拒R}BhVV6%#"p|j en(nhRene^_֦"R)s*BffM&x2aJ$N(j]R;Fi (z\.}Z}>Fg-Xs橋iv'oœ&4h>vaV ꦜTV|hM *eU(ivnhB(eި 9J:mj*L ghqV{.g^ejB+$v&'rB#r'>hYJp'VBv褪fkZ+Oj֬%`,-w --&.-6>-FN-V^03 v|MH~-Cmۖ-X}IGNlC( T@ C@xKb,mD{F<Ķ ghK2.䒤FpVDv\Jd SKLԄeN =TER,EKSDTLEtGWſPY\+Jk^/{Ni^1lNr/~onhLn`a c8dHeXFh hlUef' k0 nNX/Gf7p`k_h KPk ,'Eq#0ޫKtuXGvpGx~Cei@cV+NwvhpX"X+ik1:)CfO~/'q۰z"jpX BJ^DЀ4 &n0p?[6+%2f2+ Nk)02n/-#/ e(QhCr ?) fq}[s0CNJih2N,s; #I0@p ')Tb^A˓\ usdY,o6b/:ﯞ+ƎiN"H*Ď4@@ASD?0 gEQ+D D }1  xI}kxg^42 q tZf$ZkhZfW4tA(s(Aȇ 0J_RR'@  X4u-b4Lh"nkpb9R,!nY2g7gs®\ ]DO#Ȕ B9!wCq{ $/6Evn̪6kLm\6z;޳[/Mo)kŞ;,mwZvhMC7V4-2׀MF&>񀻱*>.+渍ڰBh8ES#N^ l6΢6=, uz{#iql{`89'/97?9GO9Wh-R#\C5T11t5@\$N(9M/ n&58$0qզ䂞CJLyʅ&,A/D6Z9QzB&@nUDMz706sWI^D*ou\h:ݦ$TC҅#P5TæzM DzfD.33/0k*AAA=8Ё%ă>ҪбZī6*(2|jt}z2+B8"9Y(|A 383 A5gAKXU"dy\4+B0A5h<h™O' d5Cy3L{-Ap;A?4+%C05>4컾~F>\;3XC;850@h<:7<‰A2C;1h1BP:@k/plLڭC4xv8#Sr5!p*5āpցGl/cycɱ,|8qH2$qң5/?%G KdiXL;.I!:ڤ"@\BE.'o%/5yM$N4Py˸#HP@Gz}U.7kn2 ?fQ[`WQXđ$_®gvJI~ P#[/X#6;6lwY;$Cl?`ܻSnG]Ԏ@D҄av0k9Z7P]Dk/A)(#_`ew%~k)2"hh/[dn2znLSBC"H#rRVI&hݞ/= "`!@aHrxAJB|VB)1 B`P"BAb%l,V Bz@ &"2?0"@`8,A!Lʢ*$ҠCj$D>06A-Jj/zh*%ac!AbnZ@/`: p 8kG({C_ h WZvrzr~2((kă7)]p)+P j.$Lna!FDFP Q"zI(T !X q1s*.*&=@%%x-, m Ek^4E"_`AR33=4e844+eRla5[S\0S.حlB(e.Hha$5l8!FEr"p Š˸V^ o2(Fx! 12!bP1"`Fq:C>r$"0r ~PHDB6Bbbjn!0#8" ܬ>#idxATIUG`P@5 )@IxB EpH߱]dѯ3h`*bF~8KA/mE(IRbeôQUqV57LjIa,pEB67!A\yWPղȕ3!X_St[|Hc\ r@QFSZ(!$1" #-PH0!$mr!gQKP$~mTDXYCPf<p<bZD&ic6! ,! ,! ,! ,! ,! ,! ,! , (*\ȰÇ#JHŋ3B$QǏ CIdCM\ɲ˗ QI͛8eɳϟuJўB*8_P &5េV5CZDQAcu%aБ@_*R84np.^|%+Q?Y DT#N'&ktP*#84$IPf0 ,fAbBE$PB`~ʼnP 7qGbmVF O;rkpz%+k/ k\$/W@,t HCcmS2D AO;'5M|/ik'Tй{Zoo Uӝ5 o |1:ycUf?BF.R'2d᪩]pn߯Qp$uMmPkӝvnWhJYs5,y(A-DrאG ! rtA>P oEBp `RK_;)( %v ES 4d>Y=DHE 6D$BEw ǃB%kaoցgS(֑*rI"862@rč" DԚ @$,V8 jɄ[H L4 GH1ꈡ,0\"l R!9:EeE.1:yus"E' Fs^ D)Ї-ؼ?WІ:4$[<JъdͨF)"z HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7z hGKҚMjWֺlgKͭnw pKMr:ЍtKZͮvz xKMz|Kͯ~LN;'L [ΰ7{ GL(NW0gL8αw@L"HNϚ&;~,e(V&0L v٘ 2y43- | 4E -D0@(,HWpYX@j>7u@6@, S? tcz&z -؀@n X4` ` 4@ 0 կH%=J#Ȧ9]HA"p`?`g LiA@* T@ʢE ##@=ZjϪ~6' (耛mk`ܼ h t#pv\iN nd+C--n|` %'؀]PK[Fho4Z (s큔+x٤3Uԥ>@2md}  $exV7_snܣFIretNM/@AZсo@t앖A-s+L /;ڔH2K[ E|SgˀkN<u`q>k>QGi6@R or e ?R3,@C-6tR!p%wy&7#zg}$u~a{l{~Kowx7s޷̷yӁ*5P}q&~+("77"`xehvp'!r޶o(&. ay7=IHpoW8 ,!2(OsR(Q2'%%,lA7tQiHl:J5yvf`xMq}*$x%[u&$h`"l`)EkFo`ooH5T|h[-@p#7UX[#PkFU_Ø_ň_HT! ,! ,! ,! ,! ,! ,! ,i80 *DP!AhP@-bfFD.iU\PRq-2!xG+NYyF<Ůِ jFTԄ@yVcc oy`&kJ*ȍuƢƅBD)! ,! ,i8)\CJtH"B )hcC48v dH$K/I.W$I/pi/@=ђb%b@! ,ji80 *DP!AhP@-bfƎ5bY o82_8jŒ"It<Q"j5dcɢC<*t)Ѧ>EڔUbH#6Ԋ0 ! ,! ,1 H*\ȰÇ#JH`1VȱǏ CIɓ(S\ɲ˗0cHS͛8sɳϟ@ :FH*]ʴӧPJhԫXjʵׯ.^KٳhӪ]{Rl6pʝK]n˷߿Ziy&\<XL`^KLiU\3v,vh̨S^+kawaof& }. ǃqhm_f{֣سkGIY#*Vȵɲ+A*Zڽ k 7G,Fgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZR iFa \2j~OB-8`L aʨhh rJ%Fri$rKY3C24K)fb&ҠA+3]cD!-p" 8Ir@WЩ3uꋝ8sI3y2S֎pIqc=yX!<5_'U;)uI % xȤQ@rpIt ix 2RLTGcPqb!EK v4"mFcNTJHIM:bk-jBjɦ HA: yAЎ$ՔKEWmnP3%0ܠOqRC0l AuB bU8 bh3^GaqπUc)B:hZ69ti%{ZִhVTZư cp#CT}'UX= P.&5jfXYLy|wؒ|.ҙĥV 9/QV3:DkBuRNih$O d,dQ'l ,5PjX ;6$O"p 5;lQmAٴ.kj3LݚA=“4hvp8ǺOsQ; + \֐i 811#oB\Rg&VA9v7DPUl #YU`s()LT`\vNTBnTDVa(ts" P֠xS5˩b7zg?O?TJAz<}(3h B.LiH ҃h`1R$REr4H7j\&@T4D1DnCK /Jی(VO /ȕ«PTԱ ĬjGZV58+ zլR , {Tf~=c̕e & cVhGAk $mZul N7yHH('b) :}D8PѲa1bx.@p y3ǐ] nQlH~я m, bU8LB*'JTLH !@cw ,V1s Y0@Na V,Å3pS6#.?X-&UȘ6V4 )! oq~4 rX LʛO.αfFnFuN5;`u& k2y(oZ ug_7"137*>u44HAZ525ES \X&F<$,B h ݀m! X& k;FHuBWp@7YǼI虖"xa?VtF?^q -s64j s l#x|Hܔa!\Anxun y1VW( Z;'\2F[7I:U<=D,#pxcL =a7~? İL5j+hЀ x7կB|l fti/x 8S/.p]j Пp-Λ}_xCz䍽UZSkg^ hO1kFjxG?JhX rbbaя~c^&as+w HT"NS*-X8/[I'ϐpp^PCPe 5_qLkC%c,N1f%Q77ܑL7PNc<:F K9JPegP;SWCsFx7Hӄ^P(vK SX*ul}`q5ee!fSŐLtpw nb5 63jxzņ'EBy?~zx*Xɧ}Fp!({iX2(B&z7k}(HF'($h/@ݷ&np!&[^GjT k^ۈ4nT2Űn] 1ro= ^1$ȁ`5@4 ˲_.jSXrE5Ѓ y:67 &6WuYHP7rbf [a0)QHvQBhwL+ْb195yPd(9iRkge04y^@Yfe8EaP t(a P1"~ P wVPA wA~Ɗ|HApu AkUEW(/d})i)4Aǘ}e'D` k! !CZ2w d ?ǙܐIVE2TL3CbfΤb3L'w[X^^9TıgK'ibfqH$1ȃ%X: <7PMat&!9BP( dcZYc"J`@a Ն@3W9s5É~W OLtyZ(Tgr3R#4 #<-5`!M&O s74A %7S4PW sO=Y-MTN8:ք7BdY0c.V ѱ#M){$ zhN)?i +T,P_ 0wd & np&!?bkgy{7e{ `|n "@ L{wy,o{?`h۸mo븅KGKgCl f }Y PL 2p0{ !0йIƋ%$F2f1snW55oL*i!>1U#t.^!@O7 E" q `1o8Y`8DtI9Ŕa2##A%aI6#D)C2TS̽OTA1aL'hf+@[9TDܙFf񿅘Sk0'@2Pm =U)@Z=M>脞f+6 ov27 ]3^>>-0 )>>}`ֻ>=2$ ^~-^^5@@1ѷump80.P.`um Nݰ 5m bNm_'0=;g _&;(,~.2?*?6:<>@B?D_F8JGNP:T_ -XZV^[b?SfAj\"pl?tnq_|OP pp N P} ?OR _j\ ; > o`C<O@  O _ _-C :oCly @ OU35 Fͥr RfPDQDC55S،5]SH),q?hШySNUTU^ŚUV]~VXe͞E iSH'bd)]Np1lS.6Dt)P7{S3?՞FZj֭][k"'zl7fĉ. +'Wo)y(sJiT栣tt͟G^zݿ?krWG\w[+?1n˭鲂2 ϔ*/1DG$DOP]wW_*k+'U&M-Gf b+c ]0&oA/q4UfZ55;Z]ju\^{W1%Rv_8`'j޳7afƼDb/0+a?9{|0M1F9e5Ud_9fRfo&xfwgk9hhFޡf:㤟:jj:kk;l&lF;mf3wj+)۪۾ 4p^c#]泚tLV]Y-^=SO-Çg.9H#_M(y#|5BPV! B$0u9,ifCM0DvD\ >􁰆+ <#o@\ 8牦Z #*Ky\l#U!JC(k&hjN$уXV d'jwf_ iIM1Oڢ%LjXo"W)F53(o(t+%+CYG`fvZ^>1ъ,wIjtb59Kkpve'wqLI ~Ara(h Ph&(M:K yLN,%SZѡ AWk3 ) =<_@De1|2IZqј%2P,i ySi9 /@J#4'Ro0A'E$_yRYFFjl㷻ñ)LjE2 LX+]r )CM~@,"5L=kϞaJ.^.Umc/ړ+rB:WVvʩ}+}cJ+76,5Ӫ%t0KWﲕ')ռԒ2ױtM95M/x@7`@a8:HF:RۃDWtq7vte`[nAs.o~ku;ЧF1Qykrxҝ)((067KtbݽYzsz^Nހըcpm*x^2|^ʛL+(jRXx́Rb !o}):7@041]K 5Ce+{n!gY]m-q]Zؕ֐C=dƽNK!\N$\=&3,Sծv{ۛ&6O >ӄ z7 )_}gO#?ůg8Ijx~D( Iӣ{7&}w?~Gvv?O| 4 Dds@7!E}zy$A@Ӂ ,A| J6 SC ʠ  _а_0og(= b ȅ\+ "8kgn_;@ڣBr : nkGQ`0C{j#"8+Ag#&!DG>0tУg=j ?SC7ɰ pܶ3pôjЄjPٛK4ZIeKfKgi[ k6m sbG jӄQ LԄEܣk8HD'7ȧMԣ`P\Bpv@fX ˥<99 9p:G@:qPf-ppBc KO 0g(,v`CM }cyEP>+72ѷUR! ,! ,{l}i80 *DP!AhP@-bfƎ5b8p4]@8~Ѥc0vBNQq eď8K ,XҨ7Q+))RlYc>%b@! ,li80 *DP!AhP@-bfƎ5b$8pܨ8X.$H@Q31hWfN;{ ɒ"u=|H[~2 H 769hD-m֤iAc7+hNq3J wH! ,! ,sV H Ai@C` pៅ6&I`p ˆ(S$F:\+cʜIsI4 B` 0%46$y㈁tTZjI0jj %tv̰ %8U2$XErH*ÉL7M$SJNJrA"D{DɴaI8BޒSe\Q#ғȵ<!dOZ^^FO˖G" 7* .,(.G@(8B׊,C@-EZewRIY$BB(R` rJ:,U#T;t T_ia߉2%Ptk $\#xf@A ?)P0eю8LC5B Ј (XFV@Av-d =U<ʐ &$AXhd< @}4X(b]hbhwf!9CDM"`"*7 Mg MW~%9d8V/">D nç0{62P`UH!jiID$ @*`Lz#@5 9.S588$w^[FoCxcIZ$ 8x0ÓEw ,ȥjg(&10,s.k<8\s9s;80HltI7UɑM6P-C/\;L2$da 8S5Ak?SNL]sm5m3$wAm tM;4M;T7FgC+kg@k$^@lWɡ? ]@9M7 9S@9୿.6N;ҍ{3c.4|9uD 6~ ?dDo!8 _=?/ 86:ֻ+2?im%LP"}~& >.83ꋄ93PX42s gH8̡w@E;HDE+ pa :LQ > 9qU%8E0A@ nP!zpDqt5#$`)C8D;`WY#4xÃjrHuXldJ `p]q1@pЎgNp$4r|H yh?FCPA6cE`VhǼA ƨ$Mb|`K"n8P֨LP%t#up6Z4 Rkj4drÜÁ$vr qл΢Crln,9IPs}id" qbyMR 8@G. *҄9)3QIHc%H Sx?L! ̀#"nᕯdԘ ojClqR ! blx g"84Śxu@WŶ)T;7Lb TTA3'[D nC0xܖnI:#Pp $rĂ Q+dvjB70ԗ7@K7@ :P_M B=HܜQ'HL&v G, pwǢÏN(,0,4l8<@-t m8 :`3CD?̬`mds Cw}5x$t9r0 $ A1A$5p77.cs̀MxYo-:m4I.L;+L}堇.褗n騧ꬷ.̑p8NT 8`H?dC|1@!W+T$iD  UG"dN7&K WD4|Њ[8AX)W=q{!l !t ".z3d pk"ZT!y֫ ŕn0 gH8̡w/i'< =| =!{ NQ>؝/}XB>|cCPl#i@:*xAi5CȐ|"H! ,! ,! ,lPi80 *DP!AhP@-bfƎ5b9R$E'[ǒCƄPfM q>9I(l8a@! ,l$i8ưaCJL"D3bA9>HdHH4IJ-EF$Ѵ2ebգ/cytY~ݔ)5`1Z?"t UjO&]9S>5u*GybRNRl"0#hM-[#r9>(dgkfȞ tpYD jȵ20'۹0FN7bit=RSkwe@! ,*s VixB*w H`H(bE3jcqpe\1ϳ;b&UʜI͛8sA)+6/)9IƔc>M:KRjʵ׎648 4iE,עB"Pbe- 8o I&7j6,!ܺU1~@˘&|g_([K۸s!YeX_o2qAF73 $ j).8ZVYs:ywzDA:knhM+h`o-C+tp.XIp.`D0Q !DD'PSBAPyՈQ.lZ" !ߌwH! …?c!0P d eaPMeQMc_bx7LT4I.R !LpKJ4䢌~u@Mw#PЂ}Q p9RJ@8\Xc/CDEC UP!,di H1@BkF6j3 P0Q%C]7TTTiTIJ댆fY"T-tRRB$%d';@M*ZQ !d*59Cl~H"Tg \k j #1@4I 4WErF'|s\B簢r`n 5np"SyJ&/u,-U/Bȇ]\{-#tk3YVQe^"#W.,ސ/ T1rcM a8ps$L@kÃ1NL.zݸ;F8/d\7757m?ogw/a觯Oto> G@c _:c  "p̍7 z++G lW]c5NX8q pdk0$ъrdj Ha وb qCTHQr@q 5\b36}z)ak`#PlCh&&L6 4j`7xrQ[Gq n A&)/ьu(l.u~x B,$.iS dbBYQVCy||&3i>@bJ0Dzc]=URMOx4>ʑx4DJb`%DB! '?$r -j&uNVS /M%lؘ08,X]2:Jc*p8fهn AKZѲ5jWֺlgKͭn7$FQ~}$$9Sv;=JK3iḽ́Y\ oЂ\r.$LAVLGиk\Q=E1xbXqXd$#Ay._W#<(:<pv& F؀6 v{ .I@H$D7dkDbȯH qg@:<: b\ B$N8I) BP[Z!!W|"c8?T  ҭ\prv<y`ƽiT|ȡ&IjH p04p#J ]&ŘipJW ?XV9̚%uԧɵd~4B؀1q!fluլ_=mn(7 SZ\!kaĐ'14GK5#cD5p`{ H jH+~,l4+qƥlcut'D Pcȃ(Qܫ8DkvK198VutQ.5:`:hQAX0)b +*jne!#Q]v!Unȍ(KǸɁiOOozjK?Dîd[#]fXiVgO޸PV|)tƒ4<#y4 [w1+ˣ)9}Gbp"8p J,y ]ٳ]^0^u^ep>e `_Q az!  ` #5 .hc8}sCX)hs V`j~q #asj_j- q_5_ dQ{Qaa b3668ndB2A |@"vnF`xdQy@i 0?:x$S9qeYef@prv-Unq6g.08 Ј8&(1y0.l 2p$"&sPgwF C1!AF.cekg6P+)# v^vhhkik #vjF/Mjp`&ܐk~( Q9 |rc!jz'ldpA$.<7. ʨl}2*mW qqs{DkEl*mmئm/ }@:+İ+4p(]'}*R*2rgmw֗h&rl6fiYnɍsg n0y[Voqp --app'i&4)0B'i1ky"-8Qy"QchbԉqVhyvqqq14 s4gs!Ja5wsٚp1В8&c=sesb"0I @̙:1 !r.cu Ns$dYjzIrtktVt uxvwljmv) q #d#(gc԰+rg40wavP C@= h4 1 prʥ*2 K4s:p Zwphnx x {}GxWxۗxx9G:dƨa a::q 3= zz-Q* ?Vxa{@$dcDZ"h暜 :x"zO H.)i衈&(Kn.(7 f9LA@C CP(zMJƢꫮa:䀃Ӕqw7xß@{M juy#MZ8#+8*$ $k"nk,l' 7G,Wlgw , u ((rQ8$0,4lsQVT?=c8,_"> 7xLWlfV,c? 4PMM-O˯)z=vc-e!@]?v;[?a 8H7JnN-[m-9椗nفxl:.;e"@=LwmXcW>ğyH@0#8 [<o*#Ǝ@pw$g/o HL:͵pe =Y&]-kP:H;[ W(?=w gX>N [\hpK\X.u8"MAv<H`EO [^.z` H2hL6pH:x̣> پ &MhQCyDLjn}^CIO4 B!~e(W( x!4WʐLSd AxbIer! Әdup8ofl;)Zfor(@xFy鄤9"Вtx_'eQATMBІ:D'JъZͨF7ю*K-AG@Qt%"IK $f1ƃbӐ(['wvKP(cuy)O} ācai@ITXH)G\Ô|ّ \j@r㛬J*Xc FpR'UߓFN!L $J@ Cϼ"Z9 aФH579,u 7 7jYsRX!C5'uTA[^#Dlik[KXGZ{I--g tD -tdz' Oe.@ӠIv NNHR^7(R_zB~RA|;N}JO[buՀj\_5Eb]N0 ,V1ѷ+-Y]1`K[UWboB©1'"HN&;PL*[Xβ."^n}R'޲{̍9LHψD Qs 4 #}fRf(9)ICWksp-bK5iN 0Ψn:)HU?A #չJ'Q7RO?vj>zV1a䤐a*S֖E%Q<#9LU`֭:*\> ~:iY sd6x;81nZ[/+}S[#Hƶa_>6As[:@NqMEcaNX0mh R%At> ygRrH7R;ؿuky6g;8T^1|Dƒ?.o92Bjӭ <[n(]h5iϨjF/H,x{ b/_Zw$gG;hf wyō@>_汝-p;moe0x4h&Ns)2&7 )nV4 ^_Y!aDjjXrK`q(VKxZXĆvMRyNܰ++X+±+ ˘r2+vNc7:5ЋX:Tl&IPA:ک Oԗ dC @ʪ3PUjr&,Z{zȚʺڬ:Zzؚںڭ:Zz蚮꺮ڮ:+ zJ`_į^*\Dj KE /r,I(D g.+4@ BN.%Sq-93&4&4[mq45U@Emߑ ɀAjOe1x1ֲMqQUqUmoI#q>6ADZf V* p 4 ˥/dEqM15Xk"XZ׃LǙau9Xy Vp]4u@g;#҇T7@|8{ Z!UwjJ='5p 섪Zys46K;hRug{oB%! Ϸ`S7RWȼ>}kμ3KesL>eC~̂l=e3ʼ3c>փ>P]-=]}κ;XTc M8&))`cߑ8U >-716JUy]iaH?{r]~DP /SV6K{y])`D8 c4f@r8 q}3 lVI`YVb=0C \[7 #9P *00T;WKӣ׈)HQ|U6t'=]}ȝʽ=]}؝ڽ=]}!}0,] M3K`,m8;pFV̓Β[ /ܻь<{;"/|،,~<0N/.}\9:~ >^/] Q =JPR>T^V~ŤcQ\NZ~K1\r&抢2b*4,Acă"[!a炾0/_>$L#G1fMenen^.H過r柮>Z^^qp11smi>~...~9aÖ>+)[[:pL.5 7+3 iA9mS~P'072hVnc4mDf?QDZ<+ Od> 8>~N럎^/JώtN^4LjQH_>F?/NhN뭮s>bOoOع8`PrNnF'#ooo#DDbn4ppx%Q>O7@)Ppop\ FɎJob[[/O5gɱo{QvcOKM^_DoM|OqAC@`A :|p`Ã+ "„7nx1E1v,•-]> ;A t R- ko zױ7{ZZm Aޞq '촧oT_%GUvPYZ̦Ug45X AlN.+//uGR B+ ltK %ݩ!< Xl cXB R0!T hp0!, 5Iqj7[fό]^'([l:zNy*"G9J}֫tEJVb٬eM{SI1hdn:Q<#)=`j1I' Qtez[Me)P7xx?b)ȓ iD|@57E'eAiRj8Ƹ!gP.oyjpH%yCV^2dAry&d'iL=c?5LhZƳ9=G Iݱ|#'5gTv>eSEY*BgˎOSPydMw*rXEAq\EL. чS 0cXzִ SU o%A!֒2$WNCW4URS{TVZYB %5 :UhAAqzC.F k=mZK ueZ׻敶E _S^HhHQgc$HEE{2KS4먁-E~`tow_~:(_&a20I qZ+&X {J [m@kOCl &F1SbQdHa)Fm\C4!_ew|O1T-</N ptj% pDV0*\sd7 al~rIleAЇ|hF7яt%=iJWҗt5iNwӟuE=jRԧFuUjVկ6p`=kZַuE-k]׿v=FFvlf;͆v=mX22tmnwۓ)Qo~Fwսn ?.=oz[ݏJG& 0e o{Rr\[|KLx5pR Crhw~s(?j{/yO3b}7Hz?$A=yGGzuG4.ФW)hg۽,Nw_htT4Ƀ:VNF=WpwOk~q}Cs)Ӓ&?/x&w&ԴU;=Fק~Jx~mC?{'kORґZę=~}pi=EtOr/4+}:?t/xM'Nn$ϴ?؆sV$4KTd@AK @@$A D`+d5AQA 2&xʴGjb1cPk8n*B\ 7!B۶.Q1ٴIhӄ9 q+t7<\-Ct0_8L3CKÖp6|jjЄA̩j`kGBl-ItʚɝJʟ,ʱCJPJ`J$ʎJɚ\IpJj'@|KJCʠL$8J|ʨT-l IIT,L i̦|>K vpXjp!HtXn8ýIqht0e$ڼ܄vIjpӼM.[֠ &hu@vXP 'jqSǽ\D@p u貔D&cpp-X2nXI n(_ŕDžkIɗEԆHȆjoho ҄JuPm$t I bpj8MP "M kH-x[hUQMP䍥!԰И^:mQ +Q,˳F#ERh+xQ'bQQcK25R՘҄8SSE9\L/=R+HȜ;}6=4TATh"ME25v 4,Հvgh3(v SU}XU?lfvTu'PNox>vdhjvTy +jƱrL :}ShN\ :awzMPdYֿ k \ XɘZі"Lыpk4  w!MʥTuR>mNXM TgN DH7xDU~ve[܄8Mp0qvX\ijbQr{EaءMwqac+rmWcGbXi^fވM)/dn'؇* Cn(K+Є_XхRQRʋdOeR6e\N]ߑߔ`1ފI1^b~"YZVBPcf]~fO\Vk>gMef&8MzohzX |gt*#. ^Mb+`|blz]jVxC2&8ŵ@QrE6h]^m]^K[,]A;e:%=k 8g(zm gF䃀!r߃Q-hjbteeYnң& f}eIlp`H,^knɜkj^ .lVgCRk̅NɀՕ*u]h$քv~ t(a%F]l1΅9K]dj@xHNv]0WaX8ZAvޚFگ>W;D1-b.زgsqͪZ۸W}aC ʗ+#/̧SY״s:Ǔ/o02cSb ?nr%>M*dYrPaB%88 {fb8ؗp",6`hHt AJES( J]0c$B I"`{6ϸw\SCh?"ZieYJ'ʭ+D5g̼/PQZ+P#|12 {5x"f-]&ڂKZ)n8Hvϥr8\@#=D~ % (^5E t\nSJf^]yvMp;1H'tC®\/xc-vAK)bUAaL`q8fms1%+h>s4ѶKжrqL`+:ĞtLI،c3\8fY}1 d 2hD\ rv/E Q5|&fr&89:-78NsON:ԣ.SV:ֳs^:.f?;Ӯn;.$;#sz;4ӹ{HowK9+C:8-^3R%)G6Л>W+W%?% y~zʿ||aV]_c!c %_E+=`!+__j` `n  R >^ ݕG^歞Jݠ+e^Jx` _`` 6!p|DB  f!`. j}9!EH J`y!ɭa"^Ja^Nb^bu,b_"ҟ'2" ^ ü^ R JY"b2r!&bb"/aa_"( ʢ'r3Fc2Ƣ#jy&F/ 18:^ (a3 # N b'b#@8ddaBj+=uEH IDHF^GnH:D._~I*H@! ,A H*\ȰÇ#J䙨3jȱǏ CI$C(MDr$˔[I͛eo<5p ]sѣH*ͩsâD tJjC ru+RHIV.$۷TI&Ys~H`4p${TGUThiDAG՗:˹珎1T%['.8aQΌp@[I[nz!dɴ$r8УKoLvaR+.jYkǞuܙF=;{o?QA %_|5PB](4_ 0|͖ V1P :puNHЅnw4pp*T .@ݗ~`@x"jߍn<ވ!$$>xnѵU¦fۗMgfpDe|X&f xa)^Fu:Qkyy )U vB_@p- P+@`O   1<:=\Y?Q"^/~` 0 ?B*iOw\h!^)L^g\F9Un^[m)^)o[ީɮJs"q[i=-'pBa' I@Ј4A %z@y QX5i0v<́fi#?$ˆ!0OGq4S2b9ۉoKR͵i+%V橃{omk'kuj{YNv. tj6K"ld&Pʚrޫ3y*4η^ Ĺ,'[49A" |%evŏfKjI8ʭRvv_}}^>wyoy xA ,뙣4ݧDGj(NP $^vFhz R ~.'߅6ӊT k ´e{غŶ q NoHƧOpp+IxA ݦr90m+8D:Z1p%kcD0Nv`]l/n#,ad(#;l:#Է'D#e>!z&V#RmM!VEqeS-ކk1ݑ3 S+(LNgL$3jq %E.#DTIRgvxᛥ<硳M{_'?Ϩܛ@Dq <|d%lV,"v3^$eT *0bueMotC]Z: Ӽ0KF#5@DM`HˀO`$P>j횪MȈm;c 3 QDC=1: > iG,B:B /3HqfA5aB5(M0:JC瑰eDDNrg;6N˽Up~i N & cNGwB,яxhC.ȿ Rr\"55 >o@vA}y,,S[D6!5džppPq8,\y`k\2ȫM X,@r!ixk6VM70_l˞g4=zSg3P!~W@j0 SC[ o|3yo>f5KIW_} v#>f?b԰p= @A @qpw@ L<0n-sHaHA7D@vm@pq Upq7π bpot7oo1xj :nb u8@H֓w "9 P €By0JZGѣL{ɣ9*7kuW Bx(T@ V ~ 0{+H4adjh;V`++(Ѳ;zw~"/{ Ր*uG'[+.۱N[ڥ!k ڑ*P{_}7kN< o*@ 6uǤ `Z0pkj3YU+ٚHŠFRǭ!G:PCP(+j 5o0*,Eﺖ'nVHsY V9ʰ nP25 5 S "{XF;D|PYFqg *dxu_7 \<밻jg 9:ܚ<{u m -ҍ=` %+''GO!}{$}ӳw)+=Z$&%A*xHDK}O:=ү<,VTX3mzG[~]M? ֫L@7+%8 Yq '}ׁݹ &<ေڦo|6  DFYlPINu AuVຯ e 3# h'):} v 1d n@> x} x&w-Mdz~<] -] ]޶ްԪc L0Mɐ|݈aҘӎ ǘZ.n>-p M(pL 됂@?0  (x'@΃$ A*Üَ[ -b)>̄. N9 ۄ9oPSo@Vb)5#!f7yࠝAYЍN9)xqg?n+8 'bT$aQ! PXi lΜA!\fწ-^kSx aFnI K v<x#&p 7 BX~\  j xp?_ ?_# ,'! ,e3l&-?4f/o]11d=5?~qy Ab~qzp1 ?R/vTn[LDmaѡ A Aa"ct{Ю":=@ '0f>S; 0P  -Wĩgŋ7ΔΞDHfΩKz8\P[1:-D"¸m){1n!E &lpu.\ZhҥMFZj֭][lڵO9&AvV̡6iU.AqU-ʄ) S ]c2eS:cœcyhǷ8] k":-&%D.%Cjp 7 %< K[.@E_1FgFo-7vk)oa Iri\@ʣ!;"fͲ<x謥0əx <2L,ILj" 12Nrhiݚ =.h* ;g5^~9C83IS4YN%p*#Hŋ3jȱnj?rɓ(S\raɄ[~|)&F6ɓ$ ;{^0bP(ҟބʔjR?"ѭ-6 jAU-(˝X=;7.ݘw$[\n5jXt ^o/17dŀ+mi٢[4YvjoI~Y/}IjwAk4੕ݵbY7?ή_;jG+νO1{ug]~x]W}w`|YFaVyaMgU][yj$Fv^5R|vk]8VXv9b88!|GjaMx5:ݷ^MXJLvKl$nr5; |R}zEgnFl,yIT|>mii~ 8*y2y! K)_E>*NF'?~8)AoBlY`UkN%sg+4-覫nO! ,! ,! ,! ,! , WiHA C(P!Â:D0 ܳ@ G X >˃@8VLfi̹fF>uMX1ËF).T! ,! ,! ,! ,;rassumfrassum-0.3.3/pyproject.toml000066400000000000000000000025151513515052500173510ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "rassumfrassum" version = "0.3.3" authors = [ { name = "João Távora", email = "joaotavora@gmail.com" }, ] description = "LSP/JSONRPC multiplexer for connecting one LSP client to multiple servers" readme = "README.md" requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", ] keywords = ["lsp", "language-server-protocol", "multiplexer", "jsonrpc"] [project.urls] Homepage = "https://github.com/joaotavora/rassumfrassum" Issues = "https://github.com/joaotavora/rassumfrassum/issues" [project.scripts] rass = "rassumfrassum.main:main" [tool.basedpyright] typeCheckingMode = "standard" pythonVersion = "3.13" [tool.ruff] target-version = "py313" line-length = 80 [tool.ruff.format] quote-style = "preserve" indent-style = "space" docstring-code-format = false rassumfrassum-0.3.3/src/000077500000000000000000000000001513515052500152215ustar00rootroot00000000000000rassumfrassum-0.3.3/src/rassumfrassum/000077500000000000000000000000001513515052500201345ustar00rootroot00000000000000rassumfrassum-0.3.3/src/rassumfrassum/__init__.py000066400000000000000000000003671513515052500222530ustar00rootroot00000000000000"""rassumfrassum - A simple LSP multiplexer that forwards JSONRPC messages.""" from importlib.metadata import version, PackageNotFoundError try: __version__ = version("rassumfrassum") except PackageNotFoundError: __version__ = "unknown" rassumfrassum-0.3.3/src/rassumfrassum/__main__.py000066400000000000000000000000371513515052500222260ustar00rootroot00000000000000from .main import main main() rassumfrassum-0.3.3/src/rassumfrassum/frassum.py000066400000000000000000000754471513515052500222070ustar00rootroot00000000000000""" LSP-specific message routing and merging logic. """ import asyncio from dataclasses import dataclass, field from functools import reduce from pathlib import PurePosixPath from typing import cast, Callable, Awaitable, Optional from urllib.parse import unquote, urlparse from .json import JSON from .util import ( dmerge, is_scalar, debug, info, expand_braces, ) @dataclass class Server: """Information about a logical LSP server.""" name: str caps: JSON = field(default_factory=dict) cookie: object = None @dataclass class DocumentState: """State for tracking diagnostics for a document.""" docver: int stashed_items: set[int] = field( default_factory=set ) # lean_ids of stashed completion/codeAction items inflight_pushes: dict[int, list] = field( default_factory=dict ) # server_id -> diagnostics push_diags_timer: Optional[asyncio.Task] = None push_dispatched: bool = False inflight_pulls: dict[int, str | int] = field( default_factory=dict ) # server_id -> previousResultId @dataclass class PayloadItem: """A payload item for aggregation.""" payload: JSON | list server: Server is_error: bool @dataclass class DirectResponse: """A direct response payload to send immediately without forwarding.""" payload: JSON is_error: bool = False class LspLogic: """Decide on message routing and response aggregation.""" def __init__( self, servers: list[Server], notify_client: Callable[[str, JSON], Awaitable[None]], request_client: Callable[[str, JSON], Awaitable[tuple[bool, JSON]]], request_server: Callable[ [Server, str, JSON], Awaitable[tuple[bool, JSON]] ], notify_server: Callable[[Server, str, JSON], Awaitable[None]], opts, ): """Initialize with all servers, notification and request senders, and options.""" self.primary = servers[0] self.notify_client = notify_client self.request_client = request_client self.request_server = request_server self.notify_server = notify_server self.opts = opts # Track document state: URI -> DocumentState self.document_state: dict[str, DocumentState] = {} # Map server ID to server object for data recovery self.servers: dict[int, Server] = {id(s): s for s in servers} # Stash for lean identifiers: lean_id -> (payload, original_data, server) self.stash: dict[int, tuple[JSON, JSON | None, Server]] = {} self.commands_map: dict[str, Server] = {} # Track file watchers: registration_id -> (server, list of expanded glob patterns) self.file_watchers: dict[str, tuple[Server, list[str]]] = {} async def on_client_request( self, method: str, params: JSON, servers: list[Server] ) -> list[Server] | DirectResponse: """ Handle client requests and determine who receives it Args: method: LSP method name params: Request parameters servers: List of available servers (primary first) Returns: List of servers that should receive the request, or DirectResponse to send immediately without forwarding """ # Check for data recovery from stash if method.endswith("resolve") and ( stashed := self.stash.get(cast(int, params.get('data'))) ): payload, original_data, server = stashed if original_data is not None: # Happy case: restore original data and route to original server params['data'] = original_data return [server] elif payload: # Happier case: respond immediately with stashed payload return DirectResponse(payload=payload) else: # Oops! This will be an error to the client return [] # initialize goes to all servers elif method == 'initialize': doccaps = params['capabilities']['textDocument'] # Check for client $streamingDiagnostics capability if doccaps.pop('$streamingDiagnostics', None): self.opts.stream_diagnostics = True info("Client requested streaming diagnostics mode") # Force UTF-16 encoding to avoid position mismatches (#8) if g := params['capabilities'].get('general'): g['positionEncodings'] = ['utf-16'] # In streaming mode, add diagnostic capability to client if self.opts.stream_diagnostics: # TODO: also force versionSupport in the # publishDiagnostics cap. doccaps['diagnostic'] = {'dynamicRegistration': False} return servers # shutdown goes to all servers elif method == 'shutdown': return servers # Route codeAction to all supporting servers elif method == 'textDocument/codeAction': return [s for s in servers if s.caps.get('codeActionProvider')] # Route location-based requests to all supporting servers elif cap := { 'textDocument/definition': 'definitionProvider', 'textDocument/typeDefinition': 'typeDefinitionProvider', 'textDocument/implementation': 'implementationProvider', 'textDocument/declaration': 'declarationProvider', 'textDocument/references': 'referencesProvider', }.get(method): return [s for s in servers if s.caps.get(cap)] elif method == 'workspace/executeCommand': probe = self.commands_map.get(cast(str, params.get('command'))) return [probe] if probe else [] # Completions is special elif method == 'textDocument/completion': cands = [s for s in servers if s.caps.get('completionProvider')] if len(cands) <= 1: return cands if k := params.get("context", {}).get("triggerCharacter"): return [ s for s in cands if (cp := s.caps.get("completionProvider")) and k in cp.get("triggerCharacters", []) ] else: return cands # Route these to at most one server supporting this capability elif cap := { 'textDocument/rename': 'renameProvider', 'textDocument/formatting': 'documentFormattingProvider', 'textDocument/rangeFormatting': 'documentRangeFormattingProvider', }.get(method): for s in servers: if s.caps.get(cap): return [s] return [] # Handle pull diagnostics requests elif method == 'textDocument/diagnostic': # fmt: off if ( (text_doc := params.get('textDocument')) and (uri := text_doc.get('uri')) and (state := self.document_state.get(uri)) and (targets := [s for s in servers if s.caps.get('diagnosticProvider')]) ): # Register inflight pulls for all target servers for target in targets: state.inflight_pulls[id(target)] = -1 # Check if this helps completes an ongoing push # aggregation JT@2026-01-08: hmmm, this should work, # but could also do it in on_server_response... if self._pushdiags_complete(state): await self._publish_pushdiags(uri, state) return targets return [] # Default: route to primary server return [servers[0]] if servers else [] async def on_client_notification(self, method: str, params: JSON) -> None: """ Handle client notifications to track document state and forward to servers. """ async def forward_all(): for server in self.servers.values(): await self.notify_server(server, method, params) def reset_state(uri: str, version: Optional[int]): """Reset document state. If version is None, close the document.""" if state := self.document_state.get(uri): if state.push_diags_timer: state.push_diags_timer.cancel() # Clean up stashed items for this document for lean_id in state.stashed_items: self.stash.pop(lean_id, None) if version is not None: # Preserve inflight_pulls in streaming mode old_pulls = ( state.inflight_pulls if self.opts.stream_diagnostics else {} ) # Replace with fresh state state = DocumentState(docver=version) state.inflight_pulls.update(old_pulls) self.document_state[uri] = state return state else: self.document_state.pop(uri, None) return None elif version is not None: state = DocumentState(docver=version) self.document_state[uri] = state return state if method == 'textDocument/didClose': reset_state(params["textDocument"]["uri"], None) await forward_all() elif method in ('textDocument/didOpen', 'textDocument/didChange'): uri = params["textDocument"]["uri"] v = params["textDocument"]["version"] state = reset_state(uri, v) await forward_all() # In streaming mode, pull diagnostics from pull-capable servers if self.opts.stream_diagnostics: await self._pull_and_stream_diags( uri, state, method == 'textDocument/didChange' ) elif method == 'workspace/didChangeWatchedFiles' and ( changes := params.get("changes") ): # FIXME: If there are multiple changes, we send all of them to a server # even if it only cares about some. Should filter params per server. for server, patterns in self.file_watchers.values(): for change in changes: if (uri := change.get("uri")) and any( _uri_matches_pattern(uri, p) for p in patterns ): await self.notify_server(server, method, params) break else: await forward_all() async def on_client_response( self, method: str, request_params: JSON, response_payload: JSON, is_error: bool, server: Server, ) -> None: """ Handle client responses to server requests. """ pass async def on_server_request( self, method: str, params: JSON, source: Server ) -> DirectResponse | None: """ Handle server requests to the client. Returns: DirectResponse to send immediately without forwarding, or None to forward the request normally """ # Track file watcher registrations if method == "client/registerCapability" and ( registrations := params.get("registrations") ): for reg in registrations: if ( reg.get("method") == "workspace/didChangeWatchedFiles" and (reg_id := reg.get("id")) and (opts := reg.get("registerOptions")) and (watchers := opts.get("watchers")) ): # Process watchers: expand braces, combine baseUri+pattern expanded_patterns = [] for watcher in watchers: expanded_patterns.extend(_process_watcher(watcher)) if expanded_patterns: self.file_watchers[reg_id] = (source, expanded_patterns) return None async def on_server_notification( self, method: str, params: JSON, source: Server ) -> None: """ Handle server notifications and forward to client. """ # Special handling for diagnostics if ( method == 'textDocument/publishDiagnostics' and (uri := params.get('uri')) and (state := self.document_state.get(uri)) ): diagnostics = params.get('diagnostics', []) self._stash_diagnostics_data(diagnostics, source, state) _add_source_attribution(diagnostics, source) # Check version - drop stale diagnostics if (version := params.get('version')) and version != state.docver: return # In streaming mode, send diagnostics immediately without aggregation if self.opts.stream_diagnostics: # Add version if not present params['token'] = f"{source.name}-{id(source)}" if 'version' not in params: params['version'] = state.docver await self.notify_client('$/streamDiagnostics', params) return # Non-streaming mode: aggregation logic # Update aggregate with this server's diagnostics state.inflight_pushes[id(source)] = diagnostics # If already dispatched, decide whether to re-send or drop if state.push_dispatched: if self.opts.drop_tardy: debug("Dropping tardy diagnostics") return else: debug( "Re-sending enhanced aggregation for tardy diagnostics" ) await self._publish_pushdiags(uri, state) elif self._pushdiags_complete(state): # All servers (push + pull) have responded, send immediately await self._publish_pushdiags(uri, state) # Check if this is the first diagnostic for this document elif len(state.inflight_pushes) == 1: # Start timeout task async def send_on_timeout(): await asyncio.sleep( self.get_aggregation_timeout_ms(method) / 1000.0 ) await self._publish_pushdiags(uri, state) state.push_diags_timer = asyncio.create_task(send_on_timeout()) return elif ( method == 'textDocument/publishDiagnostics' and self.opts.stream_diagnostics ): # no 'state' but still want to convert params['token'] = f"{source.name}-{id(source)}" method = '$/streamDiagnostics' # Forward other notifications immediately await self.notify_client(method, params) async def on_server_response( self, method: str | None, request_params: JSON, payload: JSON, is_error: bool, server: Server, ) -> None: """ Handle server responses. """ if not payload or is_error: return # Stash data fields in codeAction responses if ( method == 'textDocument/codeAction' and (uri := request_params['textDocument']['uri']) and (doc_state := self.document_state.get(uri)) ): for action in cast(list, payload): self._stash_data(action, server, doc_state) if (command := action.get("command")) and ( command_name := command.get("command") ): self.commands_map[command_name] = server elif ( method == 'textDocument/codeAction' and (uri := request_params['textDocument']['uri']) and (doc_state := self.document_state.get(uri)) ): for action in cast(list, payload): self._stash_data(action, server, doc_state) elif ( method == 'textDocument/diagnostics' and (uri := request_params['textDocument']['uri']) and (doc_state := self.document_state.get(uri)) ): self._stash_diagnostics_data( payload.get('items', []), server, doc_state ) doc_state.inflight_pulls[id(server)] = cast( str | int, payload.get("resultId") ) elif ( method == 'textDocument/completion' and (uri := request_params.get('textDocument', {}).get('uri')) and (doc_state := self.document_state.get(uri)) ): items = ( payload if isinstance(payload, list) else payload.get('items', []) ) for item in cast(list, items): self._stash_data(item, server, doc_state) # Extract server name and capabilities from initialize response if method == 'initialize': if 'name' in payload.get('serverInfo', {}): server.name = payload['serverInfo']['name'] caps = payload.get('capabilities') server.caps = caps.copy() if caps else {} # index the commands of "executeCommandProvider" if (p := payload.get("executeCommandProvider")) and ( cmds := p.get("commands") ): for c in cmds: self.commands_map[c] = server # In streaming mode, remove diagnosticProvider from the merged caps # (but keep it in server.caps for our internal use) if self.opts.stream_diagnostics and caps: caps.pop('diagnosticProvider', None) def get_aggregation_timeout_ms(self, method: str | None) -> int: """ Get timeout in milliseconds for this aggregation. """ if method == 'textDocument/publishDiagnostics': return 1000 return 3000 def process_responses( self, method: str, items: list[PayloadItem], ) -> tuple[JSON | list, bool]: """ Aggregate payloads (which may be only one!) Returns tuple of (aggregate payload, is_error). """ def reduce_maybe(items, fn, initial): """Reduce items, or return single payload directly if only one.""" if len(items) == 1: return items[0].payload return reduce(fn, items, initial) is_error = False # If all responses are errors, return the first error if all(item.is_error for item in items): res = items[0].payload is_error = True # Otherwise, skip errors and aggregate successful responses items = [item for item in items if (not item.is_error) and item.payload] if method in ( 'textDocument/definition', 'textDocument/typeDefinition', 'textDocument/implementation', 'textDocument/declaration', 'textDocument/references', ): res = reduce_maybe( items, lambda acc, item: self._merge_locations( acc, cast(JSON, item.payload), item.server ), [], ) elif method == 'textDocument/diagnostic': all_items = [] for item in items: p = cast(JSON, item.payload) diagnostics = p.get('items', []) _add_source_attribution(diagnostics, item.server) all_items.extend(diagnostics) # FIXME: JT@2026-01-05: we elide any 'resultId', which # means we're missing out on that optimization. Not too # serious if we can convince the client to support # streaming, which should support 'resultId'. res = {'items': all_items, 'kind': "full"} elif method == 'textDocument/codeAction': res = reduce_maybe( items, lambda acc, item: acc + (cast(list, item.payload) or []), [], ) elif method == 'textDocument/completion': def normalize(x): return x if isinstance(x, dict) else {'items': x} # FIXME: Deep merging CompletionList properties is wrong # for many fields (e.g., isIncomplete should probably be OR'd) res = reduce_maybe( items, lambda acc, item: dmerge(acc, normalize(item.payload)), {}, ) elif method == 'initialize': res = reduce_maybe( items, lambda acc, item: self._merge_initialize_payloads( acc, cast(JSON, item.payload), item.server ), {}, ) # In streaming mode, advertise our custom streaming capability if self.opts.stream_diagnostics and not is_error: res['capabilities']['$streamingDiagnosticsProvider'] = True elif method == 'shutdown': res = {} else: res = reduce_maybe( items, lambda acc, item: dmerge(acc, cast(JSON, item.payload)), {}, ) return (res, is_error) def process_request( self, method: str, params: JSON, server: Server ) -> None: """Called just before request is forwarded to a specific server""" if ( method == 'textDocument/codeAction' and (context := params.get('context')) and (diags := context.get('diagnostics')) ): # TODO: as a further optimization we could use the stashed # data prevent that context diagnostics from other sources # don't travel as context. for d in diags: if ( (lean_id := d.get('data')) and isinstance(lean_id, int) and (stashed := self.stash.get(lean_id)) ): _, orig_data, _ = stashed d['data'] = orig_data def _merge_initialize_payloads( self, aggregate: JSON, payload: JSON, source: Server ) -> JSON: """Merge initialize response payloads (result objects).""" # Determine if this response is from primary primary_payload = source == self.primary # Merge capabilities by iterating through all keys res = aggregate.get('capabilities', {}) new = payload.get('capabilities', {}) for cap, newval in new.items(): def t1sync(x): return x == 1 or (isinstance(x, dict) and x.get("change") == 1) if res.get(cap) is None: res[cap] = newval elif cap == 'textDocumentSync' and t1sync(newval): res[cap] = newval elif is_scalar(newval) and res.get(cap) is None: res[cap] = newval elif is_scalar(res.get(cap)) and not is_scalar(newval): res[cap] = newval elif ( isinstance(res.get(cap), dict) and isinstance(newval, dict) and cap not in ["semanticTokensProvider"] ): # FIXME: This generic merging needs work. For example, # if one server has hoverProvider: true and another # has hoverProvider: {"workDoneProgress": true}, the # result should be {"workDoneProgress": false} to # retain the truish value while not announcing a # capability that one server doesn't support. However, # the correct merging strategy likely varies per # capability. res[cap] = dmerge(res.get(cap), newval) aggregate['capabilities'] = res # Merge serverInfo s_info = payload.get('serverInfo', {}) if s_info: def merge_field(field: str, s: str) -> str: merged_info = aggregate.get('serverInfo', {}) cur = merged_info.get(field, '') new = s_info.get(field, '') if not (cur and new): return new or cur return f"{new}{s}{cur}" if primary_payload else f"{cur}{s}{new}" aggregate['serverInfo'] = { 'name': merge_field('name', '+'), 'version': merge_field('version', ','), } # Return the mutated aggregate return aggregate def _merge_locations( self, aggregate: list[JSON], payload: JSON | list[JSON], source: Server ) -> list[JSON]: if isinstance(payload, dict): payload = [payload] def to_location_link(value: JSON) -> JSON | None: if "targetUri" in value: return value # Location -> LocationLink elif (uri := value.get('uri')) and (range := value.get('range')): return { "targetUri": uri, "targetSelectionRange": range, "targetRange": range, } else: return None def location_link_equal(l1: JSON, l2: JSON) -> bool: return l1["targetSelectionRange"] == l2["targetSelectionRange"] result = [] for v in payload: v = to_location_link(v) if v and not any( location_link_equal(v, other) for other in aggregate ): result.append(v) return aggregate + result def _stash_data( self, payload: JSON, server: Server, doc_state: DocumentState ): """Stash data field with lean identifier. Mutate payload.""" # Stash original data (or None) and server, replace with lean id original_data = payload.get('data') lean_id = id(payload) self.stash[lean_id] = (payload, original_data, server) payload['data'] = lean_id # Track lean_id in document state for cleanup doc_state.stashed_items.add(lean_id) def _pushdiags_complete(self, state: DocumentState) -> bool: """Check if diagnostic aggregation is complete for a document.""" # Don't send empty aggregations - need at least one push diagnostic if not state.inflight_pushes: return False # Aggregation is complete when union of push diagnostics and inflight pulls covers all servers return ( state.inflight_pushes.keys() | state.inflight_pulls.keys() ) == self.servers.keys() async def _publish_pushdiags(self, uri: str, state: DocumentState) -> None: """Send aggregated diagnostics to the client.""" state.push_dispatched = True if state.push_diags_timer: state.push_diags_timer.cancel() await self.notify_client( 'textDocument/publishDiagnostics', { 'uri': uri, 'version': state.docver, 'diagnostics': reduce( lambda acc, diags: acc + (cast(list, diags) or []), state.inflight_pushes.values(), [], ), }, ) async def _pull_and_stream_diags(self, orig_uri, state, include_neighbours): """Pull from diagnosticProvider servers and push to client. uri is the URI that motivated this. """ async def doit(server: Server, uri: str, state: DocumentState): is_error, pull_response = await self.request_server( server, 'textDocument/diagnostic', { 'textDocument': {'uri': uri}, 'previousResultId': state.inflight_pulls.get(id(server)), }, ) if is_error: if pull_response.get('data', {}).get('retriggerRequest'): await doit(server, uri, state) elif pull_response: resultId = pull_response.get("resultId") state.inflight_pulls[id(server)] = cast(str | int, resultId) diagnostics = pull_response.get('items', []) self._stash_diagnostics_data(diagnostics, server, state) _add_source_attribution(diagnostics, server) # Send as streamDiagnostics notification params = { 'uri': uri, 'version': state.docver, 'token': f"{server.name}-{id(server)}", 'kind': pull_response.get('kind'), } if diagnostics: params['diagnostics'] = diagnostics await self.notify_client('$/streamDiagnostics', params) for server in self.servers.values(): if not server.caps.get('diagnosticProvider'): continue # Use as background task to avoid blocking other # servers. asyncio.create_task(doit(server, orig_uri, state)) if include_neighbours: for uri, state in self.document_state.items(): if uri != orig_uri: asyncio.create_task(doit(server, uri, state)) def _stash_diagnostics_data(self, diags, source, state): for diag in diags: self._stash_data(diag, source, state) def _add_source_attribution(diags, server): for d in diags: if 'source' not in d: d['source'] = server.name def _process_watcher(watcher: JSON) -> list[str]: """Process an LSP "watcher" into a list of expanded glob patterns. Returns empty list for malformed watchers.""" glob_pattern = watcher.get("globPattern") if not glob_pattern: return [] # Malformed watcher if isinstance(glob_pattern, str): # Simple glob pattern like "**/*.toml" - expand braces return expand_braces(glob_pattern) elif isinstance(glob_pattern, dict): # Relative pattern with baseUri - combine and expand base_uri = glob_pattern.get("baseUri") pattern = glob_pattern.get("pattern") if not base_uri or not pattern: return [] # Malformed # Parse baseUri to get path base_parsed = urlparse(base_uri) if base_parsed.scheme != "file": return [] # Only support file:// URIs base_path = unquote(base_parsed.path) # Combine base path with pattern, then expand braces expanded = expand_braces(pattern) return [f"{base_path}/{p}" for p in expanded] return [] # Unknown format def _uri_matches_pattern(uri: str, pattern: str) -> bool: """Check if a URI matches a glob pattern string.""" parsed = urlparse(uri) if parsed.scheme != "file": return False path = unquote(parsed.path) posix_path = PurePosixPath(path) try: return posix_path.match(pattern) except Exception: # Pattern matching error - be conservative and forward return True rassumfrassum-0.3.3/src/rassumfrassum/json.py000066400000000000000000000052301513515052500214570ustar00rootroot00000000000000""" Generic JSONRPC message reading/writing using LSP framing. LSP uses HTTP-style headers: Content-Length: N\r\n\r\n{json} """ import json import asyncio import sys from typing import BinaryIO, cast, Any JSON = dict[str, Any] async def read_message(reader: asyncio.StreamReader) -> JSON | None: """ Read a single JSONRPC message from an async stream. Returns None on EOF. """ headers: dict[str, str] = {} while True: line = await reader.readline() if not line: return None line = line.decode('utf-8').strip() if not line: # Empty line signals end of headers break if ':' in line: key, value = line.split(':', 1) headers[key.strip()] = value.strip() content_length = headers.get('Content-Length') if not content_length: return None content = await reader.readexactly(int(content_length)) return cast(JSON, json.loads(content.decode('utf-8'))) async def write_message(writer: asyncio.StreamWriter, message: JSON) -> None: """ Write a single JSONRPC message to an async stream. """ content = json.dumps(message, ensure_ascii=False) content_bytes = content.encode('utf-8') header = f"Content-Length: {len(content_bytes)}\r\n\r\n" writer.write(header.encode('utf-8')) writer.write(content_bytes) await writer.drain() def read_message_sync(stream: BinaryIO | None = None) -> JSON | None: """ Read a single JSONRPC message from stdin (or provided stream) synchronously. Returns None on EOF. """ if stream is None: stream = sys.stdin.buffer headers: dict[str, str] = {} while True: line = stream.readline() if not line: return None line_str = line.decode('utf-8').strip() if not line_str: break if ':' in line_str: key, value = line_str.split(':', 1) headers[key.strip()] = value.strip() content_length = int(headers.get('Content-Length', '0')) if content_length == 0: return None content = stream.read(content_length) return cast(JSON, json.loads(content.decode('utf-8'))) def write_message_sync(message: JSON, stream : BinaryIO | None = None) -> None: """ Write a single JSONRPC message to stdout (or provided stream) synchronously. """ if stream is None: stream = sys.stdout.buffer content = json.dumps(message, ensure_ascii=False) content_bytes = content.encode('utf-8') header = f"Content-Length: {len(content_bytes)}\r\n\r\n" _ = stream.write(header.encode('utf-8')) _ = stream.write(content_bytes) _ = stream.flush() rassumfrassum-0.3.3/src/rassumfrassum/main.py000077500000000000000000000111541513515052500214370ustar00rootroot00000000000000#!/usr/bin/env python """ rassumfrassum - A simple LSP multiplexer that forwards JSONRPC messages. """ import argparse import asyncio import sys from . import __version__ from .preset import load_preset from .rassum import run_multiplexer from .util import ( log, set_log_level, set_max_log_length, LOG_SILENT, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_EVENT, LOG_TRACE, ) def parse_server_commands(argv: list[str]) -> tuple[list[str], list[list[str]]]: """ Split argv on '--' separators. Returns (rass_args, [server_command1, server_command2, ...]) """ if "--" not in argv: return argv, [] # Find all '--' separator indices separator_indices = [i for i, arg in enumerate(argv) if arg == "--"] # Everything before first '--' is rass options rass_args = argv[: separator_indices[0]] # Split server commands server_commands: list[list[str]] = [] for i, sep_idx in enumerate(separator_indices): # Find start and end of this server command start = sep_idx + 1 end = ( separator_indices[i + 1] if i + 1 < len(separator_indices) else len(argv) ) server_cmd: list[str] = argv[start:end] if server_cmd: # Only add non-empty commands server_commands.append(server_cmd) return rass_args, server_commands def main(argv=None) -> None: """ Parse arguments and start the multiplexer. """ if argv is None: import sys argv = sys.argv[1:] # Parse multiple '--' separators for multiple servers rass_args, server_commands = parse_server_commands(argv) # Parse rass options with argparse parser = argparse.ArgumentParser( prog='rass', usage="%(prog)s [-h] [%(prog)s options] [preset] [-- server1 [args...] [-- server2 ...]]", add_help=True, ) parser.add_argument( '--version', action='version', version=f'%(prog)s {__version__}' ) parser.add_argument( 'preset', nargs='?', help='Preset name or path to preset file' ) parser.add_argument( '--quiet-server', action='store_true', help='Suppress server\'s stderr.' ) parser.add_argument( '--delay-ms', type=int, default=0, metavar='N', help='Delay all messages from rass by N ms.', ) parser.add_argument( '--drop-tardy', action='store_true', help='Drop tardy messages instead of re-sending aggregations.', ) parser.add_argument( '--stream-diagnostics', action=argparse.BooleanOptionalAction, default=False, help='Stream diagnostics as they arrive (default: enabled).', ) parser.add_argument( '--logic-class', type=str, default='LspLogic', metavar='CLASS', help='Logic class to use for routing (default: LspLogic).', ) parser.add_argument( '--log-level', type=str, choices=['silent', 'warn', 'info', 'event', 'debug', 'trace'], default='event', help='Set logging verbosity (default: event).', ) parser.add_argument( '--max-log-length', type=int, default=4000, metavar='N', help='Maximum log message length in bytes; 0 for unlimited (default: 4000).', ) opts = parser.parse_args(rass_args) # Set log level based on argument log_level_map = { 'silent': LOG_SILENT, 'warn': LOG_WARN, 'info': LOG_INFO, 'event': LOG_EVENT, 'debug': LOG_DEBUG, 'trace': LOG_TRACE, } set_log_level(log_level_map[opts.log_level]) set_max_log_length(opts.max_log_length) # Load preset if specified preset_logic_class = None if opts.preset: preset_servers, preset_logic_class = load_preset(opts.preset) server_commands = preset_servers + server_commands # Use preset logic class if --logic-class wasn't explicitly set if preset_logic_class and '--logic-class' not in rass_args: opts.logic_class = ( f"{preset_logic_class.__module__}.{preset_logic_class.__name__}" ) if not server_commands: log( "Usage: rass [OPTIONS] -- [args] [-- [args]]..." ) sys.exit(1) # Validate assert opts.delay_ms >= 0, "--delay-ms must be non-negative" try: asyncio.run(run_multiplexer(server_commands, opts)) except KeyboardInterrupt: log("\nShutting down...") except Exception as e: log(f"Fatal error: {e}") sys.exit(1) if __name__ == "__main__": main() rassumfrassum-0.3.3/src/rassumfrassum/preset.py000066400000000000000000000057071513515052500220210ustar00rootroot00000000000000"""Preset loading and management for rassumfrassum.""" import importlib.util import os import sys from pathlib import Path from typing import Any from .util import PresetResult def _get_config_dirs() -> list[Path]: """ Get user config directories for rassumfrassum in XDG fallback order. Returns list in priority order: 1. $XDG_CONFIG_HOME/rassumfrassum (if XDG_CONFIG_HOME is set) 2. ~/.config/rassumfrassum (default XDG location) 3. ~/.rassumfrassum (legacy/alternative location) """ dirs: list[Path] = [] # XDG_CONFIG_HOME/rassumfrassum if xdg_config := os.environ.get('XDG_CONFIG_HOME'): dirs.append(Path(xdg_config) / 'rassumfrassum') # ~/.config/rassumfrassum (default XDG) home = Path.home() dirs.append(home / '.config' / 'rassumfrassum') # ~/.rassumfrassum (legacy) dirs.append(home / '.rassumfrassum') return dirs def load_preset(name_or_path: str) -> PresetResult: """ Load preset by name or file path. Search order for preset names (without '/'): 1. User config directories (XDG_CONFIG_HOME, ~/.config, ~/.rassumfrassum) 2. Bundled presets directory Args: name_or_path: 'python' or './my_preset.py' """ # Path detection: contains '/' means external file if '/' in name_or_path: module = _load_preset_from_file(name_or_path) else: # Try user config directories first for config_dir in _get_config_dirs(): preset_path = config_dir / f'{name_or_path}.py' if preset_path.exists(): module = _load_preset_from_file(str(preset_path)) break else: # Fall back to bundled preset module = _load_preset_from_bundle(name_or_path) servers_fn = getattr(module, 'servers', None) lclass_fn = getattr(module, 'logic_class', None) return ( servers_fn() if servers_fn else [], lclass_fn() if lclass_fn else None, ) def _load_preset_from_file(filepath: str) -> Any: """Load from external Python file using importlib.util.""" abs_path = os.path.abspath(filepath) spec = importlib.util.spec_from_file_location("_preset_module", abs_path) if spec is None or spec.loader is None: raise FileNotFoundError(f"Cannot load preset from {filepath}") module = importlib.util.module_from_spec(spec) sys.modules["_preset_module"] = module spec.loader.exec_module(module) return module def _load_preset_from_bundle(name: str) -> Any: """Load bundled preset from rassumfrassum.presets subpackage.""" # Find the presets subpackage location presets_spec = importlib.util.find_spec('rassumfrassum.presets') if presets_spec is None or presets_spec.origin is None: raise FileNotFoundError(f"Cannot find rassumfrassum.presets package") presets_dir = os.path.dirname(presets_spec.origin) preset_path = os.path.join(presets_dir, f'{name}.py') return _load_preset_from_file(preset_path) rassumfrassum-0.3.3/src/rassumfrassum/presets/000077500000000000000000000000001513515052500216215ustar00rootroot00000000000000rassumfrassum-0.3.3/src/rassumfrassum/presets/__init__.py000066400000000000000000000000511513515052500237260ustar00rootroot00000000000000"""Bundled presets for rassumfrassum.""" rassumfrassum-0.3.3/src/rassumfrassum/presets/basedruff.py000066400000000000000000000001611513515052500241320ustar00rootroot00000000000000def servers(): return [ ['basedpyright-langserver', '--stdio'], ['ruff', 'server'] ] rassumfrassum-0.3.3/src/rassumfrassum/presets/python.py000066400000000000000000000002521513515052500235130ustar00rootroot00000000000000"""Python preset: ty + ruff.""" def servers(): """Return ty and ruff server commands.""" return [ ['ty', 'server'], ['ruff', 'server'] ] rassumfrassum-0.3.3/src/rassumfrassum/presets/tslint.py000066400000000000000000000060761513515052500235210ustar00rootroot00000000000000"""TypeScript preset: typescript-language-server + eslint-language-server.""" import os from rassumfrassum.frassum import LspLogic, Server from rassumfrassum.json import JSON from rassumfrassum.util import dmerge, info from typing import cast, Any def _find_workspace_folder(scope_uri: str) -> dict | None: """Find workspace folder by searching for package.json from scopeUri.""" if not scope_uri.startswith('file://'): return None file_path = scope_uri[7:] # Remove 'file://' current_dir = os.path.dirname(file_path) while current_dir and current_dir != '/': if os.path.exists(os.path.join(current_dir, 'package.json')): return { 'uri': f'file://{current_dir}', 'name': os.path.basename(current_dir), } parent = os.path.dirname(current_dir) if parent == current_dir: # Reached root break current_dir = parent return None def _eslint_config(workspace_folder: dict | None = None) -> dict: """Return base ESLint configuration.""" config = { 'validate': 'probe', 'problems': {}, 'rulesCustomizations': [], 'nodePath': None, } if workspace_folder: config['workspaceFolder'] = workspace_folder return config class TypeScriptLogic(LspLogic): """Custom logic for TypeScript-friendly servers.""" async def on_client_response( self, method: str, request_params: JSON, response_payload: JSON, is_error: bool, server: Server, ) -> None: """Enrich some workspace/configuration responses for ESLint.""" if ( method == 'workspace/configuration' and not is_error and 'eslint' in server.name.lower() ): info("Enriching workspace/configuration ESLint specifically") req_items = request_params.get('items', []) res_items = cast(list[Any], response_payload) if len(res_items) < len(req_items): res_items.extend([None] * (len(req_items) - len(res_items))) # Enrich each item for i, item in enumerate(req_items): section = item.get('section', '') # Only enrich if section is empty (ESLint config request) if section == '': wfolder = _find_workspace_folder(item.get('scopeUri', '')) cfg = _eslint_config(wfolder) # Merge with existing config or replace None if isinstance(res_items[i], dict): res_items[i] = dmerge(res_items[i], cfg) else: res_items[i] = cfg await super().on_client_response( method, request_params, response_payload, is_error, server ) def servers(): """Return eslint-language-server.""" return [ ['typescript-language-server', '--stdio'], ['eslint-language-server', '--stdio'], ] def logic_class(): """Use custom TypeScriptLogic.""" return TypeScriptLogic rassumfrassum-0.3.3/src/rassumfrassum/presets/tyruff.py000066400000000000000000000002521513515052500235110ustar00rootroot00000000000000"""Python preset: ty + ruff.""" def servers(): """Return ty and ruff server commands.""" return [ ['ty', 'server'], ['ruff', 'server'] ] rassumfrassum-0.3.3/src/rassumfrassum/presets/vuetail.py000066400000000000000000000033301513515052500236430ustar00rootroot00000000000000"""Vue preset: vue-language-server + tailwindcss-language-server with custom logic.""" import asyncio from pathlib import Path from rassumfrassum.frassum import LspLogic, Server from rassumfrassum.json import JSON from rassumfrassum.util import dmerge class VueLogic(LspLogic): """Custom logic LSP for Vue-friendly servers.""" async def on_client_request( self, method: str, params: JSON, servers: list[Server] ): if method == 'initialize': # vue-language server absolutely needs a TypeScript SDK # path. Find it via npm try: proc = await asyncio.create_subprocess_exec( 'npm', 'list', '--global', '--parseable', 'typescript', stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, _ = await proc.communicate() first_line = stdout.decode().strip().split('\n')[0] tsdk_path = str(Path(first_line) / 'lib') except Exception: tsdk_path = '/usr/local/lib/node_modules/typescript/lib' params['initializationOptions'] = dmerge( params.get('initializationOptions') or {}, { 'typescript': {'tsdk': tsdk_path}, 'vue': {'hybridMode': False}, }, ) return await super().on_client_request(method, params, servers) def servers(): """Return vue-language-server and tailwindcss-language-server.""" return [ ['vue-language-server', '--stdio'], ['tailwindcss-language-server', '--stdio'], ] def logic_class(): """Use custom VueLogic.""" return VueLogic rassumfrassum-0.3.3/src/rassumfrassum/rassum.py000077500000000000000000000627771513515052500220460ustar00rootroot00000000000000""" rassumfrassum - A simple LSP multiplexer that forwards JSONRPC messages. """ import argparse import asyncio import importlib import json import os import sys import traceback from dataclasses import dataclass, field from typing import Optional, cast from .frassum import DirectResponse, PayloadItem, Server from .json import ( JSON, ) from .json import ( read_message as read_lsp_message, ) from .json import ( write_message as write_lsp_message, ) from .util import event, log, warn, debug from .stdio import create_stdin_reader, create_stdout_writer # JSONRPC request IDs can be strings or integers ReqId = str | int class InferiorProcess: """A server subprocess and its associated logical server info.""" def __init__(self, process, server): self.process = process self.server = server def __repr__(self): return f"InferiorProcess({self.name})" process: asyncio.subprocess.Process server: Server @property def stdin(self) -> asyncio.StreamWriter: return self.process.stdin # ty:ignore[invalid-return-type] @property def stdout(self) -> asyncio.StreamReader: return self.process.stdout # ty:ignore[invalid-return-type] @property def stderr(self) -> asyncio.StreamReader: return self.process.stderr # ty:ignore[invalid-return-type] @property def name(self) -> str: """Convenience property to access server name.""" return self.server.name @dataclass class AggregationState: """State for tracking an ongoing message aggregation.""" outstanding: set[InferiorProcess] id: ReqId method: str aggregate: dict[int, PayloadItem] dispatched: bool | str = False timeout_task: Optional[asyncio.Task] = field(default=None) def log_message(direction: str, message: JSON, method: str) -> None: """ Log a JSONRPC message to stderr with extra indications """ id = message.get("id") prefix = method if id is not None: prefix += f"[{id}]" # Format: [timestamp] --> method_name {...json...} event(f"{direction} {prefix} {json.dumps(message, ensure_ascii=False)}") async def forward_server_stderr(proc: InferiorProcess) -> None: """ Forward server's stderr to our stderr, with appropriate prefixing. """ try: while True: line = await proc.stderr.readline() if not line: break # Decode and strip only the trailing newline (preserve other whitespace) line_str = line.decode("utf-8", errors="replace").rstrip("\n\r") log(f"[{proc.name}] {line_str}") except Exception as e: log(f"[{proc.name}] Error reading stderr: {e}") async def launch_server( server_command: list[str], server_index: int ) -> InferiorProcess: """Launch a single LSP server subprocess.""" basename = os.path.basename(server_command[0]) # Make name unique by including index for multiple servers name = f"{basename}#{server_index}" if server_index > 0 else basename log(f"Launching {name}: {' '.join(server_command)}") process = await asyncio.create_subprocess_exec( *server_command, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) server = Server(name=name) proc = InferiorProcess(process=process, server=server) server.cookie = proc return proc async def run_multiplexer( server_commands: list[list[str]], opts: argparse.Namespace ) -> None: """ Main multiplexer. Blocks on asyncio.gather() until a bunch of loopy async tasks complete. """ # Launch all servers procs: list[InferiorProcess] = [] for i, cmd in enumerate(server_commands): p = await launch_server(cmd, i) procs.append(p) # Create message router using specified logic class class_name = opts.logic_class if '.' in class_name: # Fully qualified name: module.path.ClassName module_name, class_name = class_name.rsplit('.', 1) module = importlib.import_module(module_name) logic_class = getattr(module, class_name) else: # Simple name: look up in frassum module from . import frassum logic_class = getattr(frassum, class_name) log(f"Logic class: {logic_class}") # Track ongoing aggregations: key -> AggregationState response_aggregations: dict[ReqId, AggregationState] = {} # Track which request IDs need aggregation: id -> (method, params, responders) inflight_requests: dict[ReqId, tuple[str, JSON, set[InferiorProcess]]] = {} # Track server requests to remap IDs # remapped_id -> (original_server_id, server, method, params) server_request_mapping: dict[ ReqId, tuple[ReqId, InferiorProcess, str, JSON] ] = {} next_remapped_id = 0 # Track rass-originated requests to servers # rass_request_id -> (server, method, params, future) rass_request_mapping: dict[ ReqId, tuple[InferiorProcess, str, JSON, asyncio.Future] ] = {} next_rass_request_id = 0 # Track rass-originated requests to client # rass_request_id -> (method, params, future) rass_client_request_mapping: dict[ ReqId, tuple[str, JSON, asyncio.Future] ] = {} # Track shutdown state shutting_down = False log(f"Primary server: {procs[0].name}") if len(procs) > 1: secondaries = [i.name for i in procs[1:]] log(f"Secondary servers: {', '.join(secondaries)}") if opts.delay_ms > 0: log(f"Delaying server responses by {opts.delay_ms}ms") # Get client streams client_reader = await create_stdin_reader() client_writer = await create_stdout_writer() async def _send_to_client(message: JSON, method: str, direction="<--"): """Send a message to the client, with optional delay.""" async def send(): log_message(direction, message, method) await write_lsp_message(client_writer, message) async def delayed_send(): await asyncio.sleep(opts.delay_ms / 1000.0) await send() if opts.delay_ms > 0: asyncio.create_task(delayed_send()) else: await send() async def _respond_to_client(id: ReqId, response: JSON, method: str): inflight_requests.pop(id, None) response["id"] = id response["jsonrpc"] = "2.0" await _send_to_client(response, method) async def notify_client(method: str, payload: JSON): """Send a notification to the client (for use by logic layer).""" if shutting_down: debug(f"Skipping notification to client (shutting down): {method}") return message = { "jsonrpc": "2.0", "method": method, "params": payload, } await _send_to_client(message, method) async def request_client(method: str, payload: JSON) -> tuple[bool, JSON]: """ Send a request to the client and wait for response (for use by logic layer). Returns: tuple of (is_error, response_payload) """ nonlocal next_rass_request_id if shutting_down: debug(f"Skipping request to client (shutting down): {method}") return (True, {"message": "Shutting down"}) # Allocate a unique string request ID rass_req_id = f"rass{next_rass_request_id}" next_rass_request_id += 1 # Create a future to wait for the response future: asyncio.Future[tuple[bool, JSON]] = asyncio.Future() # Track this request rass_client_request_mapping[rass_req_id] = (method, payload, future) # Send the request to the client message = { "jsonrpc": "2.0", "id": rass_req_id, "method": method, "params": payload, } await _send_to_client(message, method) log_message("<-r", message, method) # Wait for the response is_error, response_payload = await future return (is_error, response_payload) async def request_server( server: Server, method: str, payload: JSON ) -> tuple[bool, JSON]: """ Send a request to a server and wait for response (for use by logic layer). Returns: tuple of (is_error, response_payload) """ nonlocal next_rass_request_id if shutting_down: debug(f"Skipping request to server (shutting down): {method}") return (True, {"message": "Shutting down"}) # Get the proc for this server proc = cast(InferiorProcess, server.cookie) # Allocate a unique string request ID rass_req_id = f"rass{next_rass_request_id}" next_rass_request_id += 1 # Create a future to wait for the response future: asyncio.Future[tuple[bool, JSON]] = asyncio.Future() # Track this request rass_request_mapping[rass_req_id] = (proc, method, payload, future) # Send the request to the server message = { "jsonrpc": "2.0", "id": rass_req_id, "method": method, "params": payload, } await write_lsp_message(proc.stdin, message) log_message(f"[{proc.name}] r->", message, method) # Wait for the response is_error, response_payload = await future return (is_error, response_payload) async def notify_server(server: Server, method: str, payload: JSON) -> None: """ Send a notification to a server (for use by logic layer). """ if shutting_down: debug(f"Skipping notification to server (shutting down): {method}") return # Get the proc for this server proc = cast(InferiorProcess, server.cookie) # Send the notification to the server message = { "jsonrpc": "2.0", "method": method, "params": payload, } await write_lsp_message(proc.stdin, message) log_message(f"[{proc.name}] -->", message, method) # Instantiate logic with callbacks logic = logic_class( [p.server for p in procs], notify_client, request_client, request_server, notify_server, opts, ) def _reconstruct(ag: AggregationState) -> JSON: """Reconstruct payload part of response from aggregation state.""" payload, is_error = logic.process_responses( ag.method, list(ag.aggregate.values()) ) return { "error" if is_error else "result": payload, } def _start_aggregation(item, req_id, method, responders): """Start a new aggregation with the first response.""" proc = cast(InferiorProcess, item.server.cookie) outstanding = responders.copy() outstanding.discard(proc) async def send_whatever_is_there(state: AggregationState, method): await asyncio.sleep( logic.get_aggregation_timeout_ms(method) / 1000.0 ) log(f"Timeout for aggregation for {method} ({id(state)})!") state.dispatched = "timed-out" await _respond_to_client(ag.id, _reconstruct(state), method) ag = AggregationState( outstanding=outstanding, id=req_id, method=method, aggregate={id(proc): item}, ) debug( f"Message from {item.server.name} starts aggregation for {method} ({id(ag)})" ) ag.timeout_task = asyncio.create_task( send_whatever_is_there(ag, method) ) response_aggregations[req_id] = ag async def _continue_aggregation(item, ag): """Continue an existing aggregation with an additional message.""" proc = cast(InferiorProcess, item.server.cookie) method = ag.method debug( f"Message from {item.server.name} continues aggregation for {method} ({id(ag)})" ) if ag.dispatched: debug( f"Tardy response from {item.server.name} for {method} ({id(ag)})" ) return ag.aggregate[id(proc)] = item ag.outstanding.discard(proc) if not ag.outstanding: debug(f"Completing aggregation for {method} ({id(ag)})!") # Cancel timeout if ag.timeout_task: ag.timeout_task.cancel() # Send aggregated result to client (though check if hasn't # been cancelled first) await _respond_to_client(ag.id, _reconstruct(ag), method) ag.dispatched = True async def handle_client_request(req_id: ReqId, method: str, params: JSON | None): """Handle a single client request (spawned task to avoid blocking).""" nonlocal shutting_down # Track shutdown requests if method == "shutdown": shutting_down = True # Determine which servers to route to or get direct response result = await logic.on_client_request( method, params, [proc.server for proc in procs] ) # Check if we should respond immediately without forwarding # (if we weren't cancelled, that is). if isinstance(result, DirectResponse) and inflight_requests.get(req_id): await _respond_to_client( req_id, { "error" if result.is_error else "result": result.payload, }, method, ) return # Otherwise, forward to selected servers target_servers = result for t in target_servers: logic.process_request(method, params, t) target_procs = cast( list[InferiorProcess], [s.cookie for s in target_servers], ) if target_procs: # Send to selected servers for p in target_procs: msg = { "jsonrpc": "2.0", "id": req_id, "method": method, } if params: msg['params'] = params await write_lsp_message(p.stdin, msg) log_message(f"[{p.name}] -->", msg, method) # Update tracking to include server procs, but only if we # weren't cancelled already. if existing := inflight_requests.get(req_id): method_stored, params_stored, _ = existing inflight_requests[req_id] = ( method_stored, params_stored, set(target_procs), ) else: # respond with rass error await _respond_to_client( req_id, { "error": f"[rass] no servers to handle " f"method='{method}' with params='{params}'!", }, method, ) async def handle_client_messages(): """Read from client and route to appropriate servers.""" nonlocal shutting_down try: while True: msg = await read_lsp_message(client_reader) if msg is None: break method = msg.get("method") id = msg.get("id") if id is None and method is not None: # Notification log_message("-->", msg, method) # Intercept '$/cancelRequest' and don't let the # LspLogic decide this one if method == "$/cancelRequest": cancelled_id = msg.get("params", {}).get("id") probe = inflight_requests.get(cancelled_id) if probe: # Prevents responses to the cancelled # request from making it to the client... _, _, target_procs = inflight_requests.pop( cancelled_id ) # ...but still forward $/cancelRequest to # servers that got the request, of course. for p in target_procs: await write_lsp_message(p.stdin, msg) log_message(f"[{p.name}] -->", msg, method) else: await logic.on_client_notification( method, msg.get("params", {}) ) elif method is not None: # Client request id = cast(ReqId, id) log_message("-->", msg, method) params = msg.get("params") # Track ALL requests immediately (even DirectResponse ones) # This allows $/cancelRequest to work uniformly inflight_requests[id] = ( method, cast(JSON, params), set(), # Will be updated by handler if forwarded to servers ) # Spawn request handling as task to avoid blocking asyncio.create_task( handle_client_request(id, method, params) ) else: # Response from client id = cast(ReqId, id) if info := rass_client_request_mapping.get(id): # This is a response to a rass-originated request to client req_method, req_params, future = info del rass_client_request_mapping[id] # Extract the response is_error = "error" in msg response_payload = ( msg.get("error") if is_error else msg.get("result") ) log_message("r->", msg, req_method) # Resolve the future future.set_result( (is_error, cast(JSON, response_payload)) ) elif info := server_request_mapping.get(id): # This is a response to a server request - remap ID and route to correct server original_id, target_proc, req_method, req_params = info del server_request_mapping[id] # Inform LspLogic is_error = "error" in msg response_payload = ( msg.get("error") if is_error else msg.get("result") ) await logic.on_client_response( req_method, req_params, cast(JSON, response_payload), is_error, target_proc.server, ) # Remap ID back to original msg["id"] = original_id await write_lsp_message(target_proc.stdin, msg) log_message( f"[{target_proc.name}] s->", msg, req_method ) else: # Unknown response, log error warn(f"Unknown request for response with id={id}!") except Exception as e: log(f"Error handling client messages: {e}") finally: # Close all server stdin for p in procs: p.stdin.close() await p.stdin.wait_closed() async def handle_server_messages(proc: InferiorProcess): """Read from a server and route back to client.""" nonlocal next_remapped_id try: while True: msg = await read_lsp_message(proc.stdout) if msg is None: # Server died - check if this was expected if not shutting_down: log(f"Error: Server {proc.name} died unexpectedly") raise RuntimeError(f"Server {proc.name} crashed") break # Distinguish message types. Notifications won't have # id's, responses won't have method, requests will have both. req_id = msg.get("id") method = msg.get("method") # Server request: has both method and id if method and req_id is not None: log_message(f"[{proc.name}] <-s", msg, method) # Handle server request params = msg.get("params", {}) direct_response = await logic.on_server_request( method, cast(JSON, params), proc.server ) # Check if we should respond immediately without forwarding if direct_response: response_msg = { "jsonrpc": "2.0", "id": req_id, "error" if direct_response.is_error else "result": direct_response.payload, } await write_lsp_message(proc.stdin, response_msg) log_message(f"[{proc.name}] s->", response_msg, method) continue # This is a request from server to client - remap ID remapped_id = next_remapped_id next_remapped_id += 1 server_request_mapping[remapped_id] = ( req_id, proc, method, cast(JSON, params), ) # Forward to client with remapped ID remapped_msg = msg.copy() remapped_msg["id"] = remapped_id await _send_to_client(remapped_msg, method, "<-s") continue if method is None: req_id = cast(ReqId, req_id) # Check if this is a response to a rass-originated request if rass_info := rass_request_mapping.get(req_id): _, rass_method, _, future = rass_info del rass_request_mapping[req_id] is_error = "error" in msg payload = ( msg.get("error", {}) if is_error else msg.get("result", {}) ) log_message(f"[{proc.name}] <-r", msg, rass_method) # Resolve the future future.set_result((is_error, cast(JSON, payload))) continue # Client-originated-request, do forwarding/aggregation request_info = inflight_requests.get(req_id) if not request_info: log(f"Dropping response to unknown/cancelled {req_id}") continue method, req_params, responders = request_info is_error = "error" in msg payload = ( msg.get("error", {}) if is_error else msg.get("result", {}) ) log_message(f"[{proc.name}] <--", msg, method) await logic.on_server_response( method, req_params, cast(JSON, payload), is_error, proc.server, ) item = PayloadItem(payload, proc.server, is_error) # Skip most of aggregation state business if the # original request targeted only one server. if len(responders) == 1: logic.process_responses(method, [item]) await _respond_to_client(req_id, msg, method) continue # Response aggregation if ag := response_aggregations.get(req_id): await _continue_aggregation(item, ag) else: _start_aggregation(item, req_id, method, responders) else: # Server notification - let logic layer handle it log_message(f"[{proc.name}] <--", msg, method) payload = msg.get("params", {}) await logic.on_server_notification( method, cast(JSON, payload), proc.server ) except RuntimeError: # Server crashed - re-raise to propagate to main raise except Exception as e: log(f"Error handling messages from {proc.name}: {e}") print(traceback.format_exc(), file=sys.stderr) finally: pass # Create all tasks tasks = [handle_client_messages()] for p in procs: tasks.append(handle_server_messages(p)) # Forward stderr if not opts.quiet_server: tasks.append(forward_server_stderr(p)) try: await asyncio.gather(*tasks) except RuntimeError as e: log(f"Fatal error: {e}") sys.exit(1) # Wait for all servers to exit for p in procs: _ = await p.process.wait() rassumfrassum-0.3.3/src/rassumfrassum/stdio.py000066400000000000000000000063151513515052500216350ustar00rootroot00000000000000"""Cross-platform asyncio-compatible stdin/stdout. On Windows, this is unfortunately more complicated """ import asyncio import platform import socket import sys import threading async def create_stdin_reader() -> asyncio.StreamReader: """Create an asyncio StreamReader for stdin. On Windows: Uses run_in_executor with blocking reads. On Unix: Direct connection to sys.stdin. """ loop = asyncio.get_event_loop() reader = asyncio.StreamReader() if platform.system() == 'Windows': # Windows: Use run_in_executor to avoid pipe issues async def read_stdin_loop(): def blocking_read1(): """Blocking read1 from stdin buffer - reads available data.""" try: # read1() reads whatever is available, doesn't block for full buffer return sys.stdin.buffer.read1(4096) except Exception: return b'' try: while True: chunk = await loop.run_in_executor(None, blocking_read1) if not chunk: break reader.feed_data(chunk) reader.feed_eof() except Exception: reader.feed_eof() # Start the reading task in the background asyncio.create_task(read_stdin_loop()) return reader else: # Unix: Direct connection works fine protocol = asyncio.StreamReaderProtocol(reader) await loop.connect_read_pipe(lambda: protocol, sys.stdin) return reader class _WindowsStdoutWriter: """A StreamWriter-like wrapper for Windows stdout using run_in_executor.""" def __init__(self, loop): self._loop = loop self._buffer = bytearray() def write(self, data): """Add data to the buffer.""" self._buffer.extend(data) async def drain(self): """Flush the buffer to stdout using run_in_executor.""" if not self._buffer: return data = bytes(self._buffer) self._buffer.clear() def blocking_write(data): """Blocking write to stdout buffer - runs in thread pool.""" try: sys.stdout.buffer.write(data) sys.stdout.buffer.flush() except Exception: pass await self._loop.run_in_executor(None, blocking_write, data) def close(self): """Close the writer.""" pass async def wait_closed(self): """Wait for close to complete.""" pass async def create_stdout_writer() -> asyncio.StreamWriter: """Create an asyncio StreamWriter for stdout. On Windows: Uses run_in_executor with blocking writes. On Unix: Direct connection to sys.stdout. """ loop = asyncio.get_event_loop() if platform.system() == 'Windows': # Windows: Use custom wrapper with run_in_executor return _WindowsStdoutWriter(loop) else: # Unix: Direct connection works fine transport, protocol = await loop.connect_write_pipe( asyncio.streams.FlowControlMixin, sys.stdout ) writer = asyncio.StreamWriter(transport, protocol, None, loop) return writer rassumfrassum-0.3.3/src/rassumfrassum/test2.py000066400000000000000000000361351513515052500215570ustar00rootroot00000000000000""" Async test helpers for LSP testing. """ import asyncio import inspect import os import sys from typing import Callable, cast from .json import JSON, read_message, write_message, read_message_sync, write_message_sync from .stdio import create_stdin_reader, create_stdout_writer def log(who: str, msg: str) -> None: """Log to stderr.""" print(f"[{who}] {msg}", file=sys.stderr, flush=True) def make_diagnostic(line: int, char_start: int, char_end: int, severity: int, message: str, source: str | None = None) -> JSON: """Create a diagnostic object.""" diag: JSON = { 'range': { 'start': {'line': line, 'character': char_start}, 'end': {'line': line, 'character': char_end} }, 'severity': severity, 'message': message } if source: diag['source'] = source return diag class LspTestEndpoint: """Async LSP test helper.""" def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, name: str): self.reader = reader self.writer = writer self.name = name self._next_id = 1 @staticmethod async def create(name : str = "client") -> 'LspTestEndpoint': """Create an LSP test endpoint connected to stdin/stdout.""" reader = await create_stdin_reader() writer = await create_stdout_writer() return LspTestEndpoint(reader=reader, writer=writer, name=name) async def notify(self, method: str, params: JSON) -> None: """Send a notification (no response expected).""" await write_message( self.writer, {'jsonrpc': '2.0', 'method': method, 'params': params}, ) async def request(self, method: str, params: JSON | None = None) -> int: """Send a request and return the request id.""" req_id = self._next_id self._next_id += 1 msg = {'jsonrpc': '2.0', 'id': req_id, 'method': method} if params is not None: msg['params'] = params await write_message(self.writer, msg) return req_id async def respond(self, req_id: int, result: JSON | None) -> None: """Send a response to a server request.""" msg = {'jsonrpc': '2.0', 'id': req_id, 'result': result} await write_message(self.writer, msg) async def read_message(self, timeout_sec: float = 5.0) -> JSON: """ Read a single JSONRPC message with timeout. Use sparingly - prefer the specific read_notification(), read_response(), or read_request() methods when you know what you're expecting. This is useful when you need to verify what message arrives (or doesn't) without filtering. Raises asyncio.TimeoutError if no message arrives within timeout. """ msg = await asyncio.wait_for( read_message(self.reader), timeout=timeout_sec ) if not msg: raise EOFError("EOF while waiting for message") return msg async def read_notification(self, method: str) -> JSON: """Read messages until we get a notification with the given method.""" while True: msg = await read_message(self.reader) if not msg: raise EOFError(f"EOF while waiting for notification {method}") # Skip responses (have 'id' field) if 'id' in msg: log(self.name, f"Skipping non-notification: {msg}") continue # Check if it's the notification we want if msg.get('method') == method: return msg['params'] log(self.name, f"Skipping uninteresting notification: {msg}") async def read_request(self, method) -> tuple[int, JSON]: """Read messages until we get a request for the given method""" while True: msg = await read_message(self.reader) if not msg: raise EOFError(f"EOF while waiting for request {method}") if 'id' not in msg: log(self.name, f"Skipping notification: {msg}") continue if 'method' not in msg: log(self.name, f"Skipping server response: {msg}") continue if msg["method"] == method: return (msg["id"], cast(JSON, msg.get('params'))) log(self.name, f"Skipping uninteresting request: {msg}") async def read_response(self, req_id: int) -> JSON: """Read messages until we get a response with the given id.""" while True: msg = await read_message(self.reader) if not msg: raise EOFError(f"EOF while waiting for response to id={req_id}") # Skip notifications (no 'id' field) if 'id' not in msg: log(self.name, f"Skipping notification: {msg.get('method')}") continue # Skip server requests (have both 'id' and 'method') if 'method' in msg: log(self.name, f"Skipping server request: {msg.get('method')} id={msg['id']}") continue # Check if it's the response we want if msg['id'] == req_id: return msg log(self.name, f"Skipping response: id={msg['id']}") async def initialize( self, capabilities: JSON | None = None, rootUri: str | None = None ) -> JSON: """ Send initialize request and initialized notification. Returns the initialize response. """ import os # Default capabilities that most servers expect default_caps = { 'textDocument': { 'synchronization': { 'dynamicRegistration': False, 'willSave': True, 'willSaveWaitUntil': True, 'didSave': True, } }, 'general': {'positionEncodings': ['utf-16']}, } # Merge with provided capabilities if capabilities: from .util import dmerge merged_caps = dmerge(default_caps.copy(), capabilities) else: merged_caps = default_caps # Default rootUri to current directory if rootUri is None: rootUri = f"file://{os.getcwd()}" # Send initialize request log(self.name, "Sending initialize") req_id = await self.request( 'initialize', {'rootUri': rootUri, 'capabilities': merged_caps} ) # Read initialize response msg = await self.read_response(req_id) log(self.name, "Got initialize response") server_info = msg.get('result', {}).get('serverInfo', {}) if server_info: log( self.name, f"Server: {server_info.get('name')} v{server_info.get('version')}", ) # Send initialized notification log(self.name, "Sending initialized") await self.notify('initialized', {}) return msg async def byebye(self) -> None: """Send shutdown request and exit notification, then exit the program.""" log(self.name, "Sending shutdown") req_id = await self.request('shutdown') await self.read_response(req_id) log(self.name, "Got shutdown response") await self.notify('exit', {}) log(self.name, "done!") # FIXME: The Windows-specific stdio machinery in stdio.py is # fragile and deadlocks during normal asyncio cleanup. Don't # have time to debug it. Force exit on Windows, clean exit # elsewhere. if os.getenv('WINDOWS_KLUDGE'): os._exit(0) else: sys.exit(0) async def assert_no_message_pending(self, timeout_sec: float) -> None: """Assert that no message arrives within the given timeout.""" try: msg = await asyncio.wait_for( read_message(self.reader), timeout=timeout_sec ) raise AssertionError(f"Expected no message, but got: {msg}") except asyncio.TimeoutError: # This is what we expect - no message arrived pass async def _run_toy_server_async( name: str, version: str, capabilities: JSON, request_handlers: 'dict[str, Callable[[int, JSON | None], JSON | None]]', notification_handlers: 'dict[str, Callable[[JSON | None], None]]', raw_request_handlers: 'dict[str, Callable[[int, JSON | None, Callable[[JSON], None]], None]]' ) -> None: """Internal async implementation of toy LSP server.""" loop = asyncio.get_event_loop() # Setup async stdin/stdout using cross-platform functions reader = await create_stdin_reader() writer = await create_stdout_writer() log(name, "Started!") tasks = [] should_stop = False async def handle_async_request(msg_id: int, method: str, params: JSON | None, handler): """Handle a single async request.""" try: result = await handler(msg_id, params) response = { 'jsonrpc': '2.0', 'id': msg_id, 'result': result } await write_message(writer, response) except Exception as e: log(name, f"Error in async handler for {method}: {e}") while not should_stop: try: message = await read_message(reader) if message is None: break method = message.get('method') msg_id = message.get('id') params = message.get('params') # Handle requests (messages with id) if msg_id is not None and method: if method in raw_request_handlers: # Raw handler - it will send messages itself handler = raw_request_handlers[method] def send_msg(msg: JSON): write_message_sync(msg) handler(msg_id, params, send_msg) elif method in request_handlers: handler = request_handlers[method] # Check if handler is async if inspect.iscoroutinefunction(handler): # Spawn async handler as a task task = asyncio.create_task(handle_async_request(msg_id, method, params, handler)) tasks.append(task) else: # Call sync handler directly result = handler(msg_id, params) response = { 'jsonrpc': '2.0', 'id': msg_id, 'result': result } await write_message(writer, response) # Special handling for shutdown if method == 'shutdown': log(name, "shutting down") should_stop = True else: log(name, f"Unhandled request {method} (id={msg_id})") # Handle notifications (messages without id) elif method and msg_id is None: if method in notification_handlers: notification_handlers[method](params) else: log(name, f"got notification {method}") # Handle responses from client (e.g., workspace/configuration response) elif msg_id == 999 and method is None: log(name, f"Got response to workspace/configuration request: {message}") # Validate response and send notification if correct result = message.get('result') if (isinstance(result, list) and len(result) == 1 and isinstance(result[0], dict) and result[0].get('pythonPath') == '/usr/bin/python3'): # Response is correct, send success notification await write_message(writer, { 'jsonrpc': '2.0', 'method': 'custom/requestResponseOk', 'params': {'server': name} }) log(name, "Response validation passed, sent success notification") else: log(name, f"Response validation FAILED: {result}") except Exception as e: log(name, f"Error: {e}") break # Wait for all pending async tasks if tasks: log(name, f"Waiting for {len(tasks)} pending tasks") await asyncio.gather(*tasks) log(name, "stopped") def run_toy_server( name: str, version: str = '1.0.0', capabilities: JSON | None = None, request_handlers: 'dict[str, Callable[[int, JSON | None], JSON | None]] | None' = None, notification_handlers: 'dict[str, Callable[[JSON | None], None]] | None' = None, raw_request_handlers: 'dict[str, Callable[[int, JSON | None, Callable[[JSON], None]], None]] | None' = None ) -> None: """ Run a toy LSP server for testing. Args: name: Server name for serverInfo version: Server version capabilities: Server capabilities (defaults to empty dict) request_handlers: Dict mapping method names to (msg_id, params) -> result handlers. Handlers can be sync or async functions. Async handlers are spawned as tasks. notification_handlers: Dict mapping method names to (params) -> None handlers raw_request_handlers: Dict mapping method names to (msg_id, params, send_message) handlers. Use sparingly. These handlers get a send_message(msg: JSON) callback for sending arbitrary JSONRPC messages (e.g., duplicate responses). The handler must send the response(s) manually. """ # Default minimal capabilities if capabilities is None: capabilities = {} # Default handlers default_request_handlers: dict[str, 'Callable[[int, JSON | None], JSON | None]'] = { 'initialize': lambda msg_id, params: { 'capabilities': capabilities, 'serverInfo': {'name': name, 'version': version} }, 'shutdown': lambda msg_id, params: None, 'textDocument/hover': lambda msg_id, params: { "contents": {"kind": "markdown", "value": "oh yeah "}, "range": { "start": {"line": 0, "character": 5}, "end": {"line": 0, "character": 10} } } } # Merge user handlers (user handlers override defaults) if request_handlers: default_request_handlers.update(request_handlers) request_handlers = default_request_handlers if notification_handlers is None: notification_handlers = {} if raw_request_handlers is None: raw_request_handlers = {} # Run the async implementation asyncio.run(_run_toy_server_async(name, version, capabilities, request_handlers, notification_handlers, raw_request_handlers)) def scaled_timeout(timeout: int | float) -> int | float: """Scale timeout by TIMEOUT_SCALE environment variable (default 1.0).""" scale = float(os.environ.get('TIMEOUT_SCALE', '1.0')) return timeout * scale rassumfrassum-0.3.3/src/rassumfrassum/util.py000066400000000000000000000071151513515052500214670ustar00rootroot00000000000000from datetime import datetime import sys # Type aliases for presets ServerCommand = list[str] ServerCommands = list[ServerCommand] PresetResult = tuple[ServerCommands, type | None] # Log levels (lower number = higher priority) LOG_SILENT = 0 LOG_WARN = 1 LOG_INFO = 2 LOG_EVENT = 3 LOG_DEBUG = 4 LOG_TRACE = 5 # Global settings _current_log_level = LOG_EVENT _max_log_length = 4000 def set_log_level(level: int) -> None: """Set the global log level.""" global _current_log_level _current_log_level = level def get_log_level() -> int: """Get the current log level.""" return _current_log_level def set_max_log_length(max_len: int) -> None: """Set the maximum log message length (0 = unlimited).""" global _max_log_length _max_log_length = max_len def _truncate(s: str) -> str: """Internal: truncate string if needed.""" if _max_log_length <= 0 or len(s) <= _max_log_length: return s return f"{s[:_max_log_length]}... (truncated, {len(s)} bytes total)" def _log(prefix: str, s: str, min_level: int) -> None: """Internal: common logging implementation.""" if _current_log_level < min_level: return now = datetime.now() timestamp = now.strftime("%H:%M:%S.%f")[:-3] print(f"{prefix}[{timestamp}] {_truncate(s)}", file=sys.stderr) def info(s: str): """Log info-level message (high-level events, lifecycle).""" _log("i", s, LOG_INFO) def debug(s: str): """Log debug-level message (method names, routing decisions).""" _log("d", s, LOG_DEBUG) def trace(s: str): """Log trace-level message (full protocol details).""" _log("t", s, LOG_TRACE) def warn(s: str): """Log warning message.""" _log("W", "WARN: " + s, LOG_WARN) def event(s: str): """Log JSONRPC protocol event.""" _log("e", s, LOG_EVENT) # Alias for backward compatibility log = info def is_scalar(v): return not isinstance(v, (dict, list, set, tuple)) def dmerge(d1: dict, d2: dict): """Merge d2 into d1 destructively. Non-scalars win over scalars; d1 wins on scalar conflicts.""" result = d1.copy() for key, value in d2.items(): if key in result: v1, v2 = result[key], value # Both dicts: recursive merge if isinstance(v1, dict) and isinstance(v2, dict): result[key] = dmerge(v1, v2) # Both lists: concatenate elif isinstance(v1, list) and isinstance(v2, list): result[key] = v1 + v2 # One scalar, one non-scalar: non-scalar wins elif is_scalar(v1) and not is_scalar(v2): result[key] = v2 # d2's non-scalar wins elif not is_scalar(v1) and is_scalar(v2): result[key] = v1 # d1's non-scalar wins # Both scalars: d1 wins (keep result[key]) else: result[key] = value return result def expand_braces(pattern: str) -> list[str]: """Expand {a,b,c} brace groups in glob pattern. Examples: "**/*.{ts,js}" -> ["**/*.ts", "**/*.js"] "*.{a,b}.{x,y}" -> ["*.a.x", "*.a.y", "*.b.x", "*.b.y"] "no-braces" -> ["no-braces"] """ import re # Find first brace group match = re.search(r'\{([^}]+)\}', pattern) if not match: return [pattern] # Split alternatives by comma alternatives = match.group(1).split(',') prefix = pattern[: match.start()] suffix = pattern[match.end() :] # Recursively expand remaining braces results = [] for alt in alternatives: for expanded in expand_braces(prefix + alt + suffix): results.append(expanded) return results rassumfrassum-0.3.3/test/000077500000000000000000000000001513515052500154115ustar00rootroot00000000000000rassumfrassum-0.3.3/test/basedpyright+ruff+codebook-streaming/000077500000000000000000000000001513515052500246045ustar00rootroot00000000000000rassumfrassum-0.3.3/test/basedpyright+ruff+codebook-streaming/client.py000077500000000000000000000063351513515052500264460ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test client for basedpyright + ruff + codebook servers in streaming mode. Tests three-server diagnostic streaming (no aggregation). """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, scaled_timeout, log async def main(): """Test three-server diagnostics with tardy updates.""" client = await LspTestEndpoint.create() await client.initialize(rootUri='file:///tmp') # Open documents with errors log("client", "Opening test1.py") await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test1.py', 'version': 1, 'languageId': 'python', 'text': '''\ # This is a tset comment def foo(x: int) -> int: return x; foo("wrong"); # Type error: passing str to int ''' } }) log("client", "Opening test2.py") await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test2.py', 'version': 1, 'languageId': 'python', 'text': '''\ # Speling mistake here def bar(s: str) -> str: return s.upper(); bar(42); # Type error: passing int to str ''' } }) diagnostics_by_uri_and_source = {} # uri -> source -> [diags] log("client", "Waiting diagnostics (including tardy)...") async def collect_diags(): """Collect diagnostic notifications.""" while payload := await client.read_notification('$/streamDiagnostics'): uri = payload['uri'] diags = payload.get('diagnostics', []) if diags: source = diags[0].get('source', 'unknown') diagnostics_by_uri_and_source.setdefault(uri, {})[source] = diags log("client", f"Got {len(diags)} diagnostic(s) from {source} for {uri}") try: await asyncio.wait_for(collect_diags(), scaled_timeout(2)) except asyncio.TimeoutError: log("client", "Timeout reached, done collecting diagnostics") # Report final diagnostics grouped by URI for uri in ['file:///tmp/test1.py', 'file:///tmp/test2.py']: sources_dict = diagnostics_by_uri_and_source.get(uri, {}) total_diags = sum(len(diags) for diags in sources_dict.values()) log("client", f"\n{uri}: {total_diags} total diagnostic(s)") sources_count = {} for source, diags in sources_dict.items(): sources_count[source] = len(diags) for diag in diags: log("client", f" [{source}] {diag.get('message', '')[:60]}") log("client", f" Sources: {sources_count}") # Assertions: expect exactly 5 diagnostics per file from all 3 servers assert total_diags == 5, f"Expected 5 diagnostics for {uri}, got {total_diags}" assert sources_count.get('Ruff', 0) == 2, f"Expected 2 Ruff diagnostics for {uri}, got {sources_count.get('Ruff', 0)}" assert sources_count.get('Codebook', 0) == 1, f"Expected 1 Codebook diagnostic for {uri}, got {sources_count.get('Codebook', 0)}" assert sources_count.get('basedpyright', 0) == 2, f"Expected 2 basedpyright diagnostics for {uri}, got {sources_count.get('basedpyright', 0)}" await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/basedpyright+ruff+codebook-streaming/run.sh000077500000000000000000000007131513515052500257500ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required LSP servers are available if ! command -v basedpyright-langserver >/dev/null 2>&1 || \ ! command -v ruff >/dev/null 2>&1 || \ ! command -v codebook-lsp >/dev/null 2>&1; then echo "Required LSP servers not found, skipping test" >&2 exit 77 fi ../yoyo.sh ./client.py --rass-- --stream-diagnostics \ -- basedpyright-langserver --stdio \ -- ruff server \ -- codebook-lsp serve rassumfrassum-0.3.3/test/basedpyright+ruff+codebook/000077500000000000000000000000001513515052500226155ustar00rootroot00000000000000rassumfrassum-0.3.3/test/basedpyright+ruff+codebook/client.py000077500000000000000000000060171513515052500244540ustar00rootroot00000000000000#!/usr/bin/env python """ Test client for basedpyright + ruff + codebook servers. Tests three-server diagnostic aggregation with tardy updates using async. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log, scaled_timeout async def main(): """Test three-server diagnostics with tardy updates.""" client = await LspTestEndpoint.create() await client.initialize(rootUri='file:///tmp') # Open documents with errors log("client", "Opening test1.py") await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test1.py', 'version': 1, 'languageId': 'python', 'text': '''\ # This is a tset comment def foo(x: int) -> int: return x; foo("wrong"); # Type error: passing str to int ''' } }) log("client", "Opening test2.py") await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test2.py', 'version': 1, 'languageId': 'python', 'text': '''\ # Speling mistake here def bar(s: str) -> str: return s.upper(); bar(42); # Type error: passing int to str ''' } }) diagnostics_by_uri = {} log("client", "Waiting 3.5 seconds for diagnostics (including tardy)...") async def collect_diags(): """Collect diagnostic notifications.""" while payload := await client.read_notification('textDocument/publishDiagnostics'): uri = payload['uri'] diags = payload.get('diagnostics', []) old_count = len(diagnostics_by_uri.get(uri, [])) diagnostics_by_uri[uri] = diags if old_count > 0: log("client", f"Got {len(diags)} diagnostic(s) for {uri} (replacing {old_count})") else: log("client", f"Got {len(diags)} diagnostic(s) for {uri}") try: await asyncio.wait_for(collect_diags(), timeout=scaled_timeout(2)) except asyncio.TimeoutError: log("client", "Timeout reached, done collecting diagnostics") # Report final diagnostics for uri in ['file:///tmp/test1.py', 'file:///tmp/test2.py']: diags = diagnostics_by_uri.get(uri, []) log("client", f"\n{uri}: {len(diags)} total diagnostic(s)") sources = {} for diag in diags: source = diag.get('source', 'unknown') sources[source] = sources.get(source, 0) + 1 log("client", f" [{source}] {diag.get('message', '')[:60]}") log("client", f" Sources: {sources}") # Assertions: expect diagnostics from all 3 servers assert len(diags) == 5, f"Expected 5 diagnostics for {uri}, got {len(diags)}" assert sources.get('Ruff', 0) == 2, f"Expected 2 Ruff diagnostics for {uri}" assert sources.get('Codebook', 0) == 1, f"Expected 1 Codebook diagnostic for {uri}" assert sources.get('basedpyright', 0) == 2, f"Expected 2 basedpyright diagnostics for {uri}" await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/basedpyright+ruff+codebook/run.sh000077500000000000000000000006661513515052500237700ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required LSP servers are available if ! command -v basedpyright-langserver >/dev/null 2>&1 || \ ! command -v ruff >/dev/null 2>&1 || \ ! command -v codebook-lsp >/dev/null 2>&1; then echo "Required LSP servers not found, skipping test" >&2 exit 77 fi ../yoyo.sh ./client.py --rass-- \ -- basedpyright-langserver --stdio \ -- ruff server \ -- codebook-lsp serve rassumfrassum-0.3.3/test/basedpyright+ty-completion-aggregation/000077500000000000000000000000001513515052500251625ustar00rootroot00000000000000rassumfrassum-0.3.3/test/basedpyright+ty-completion-aggregation/client.py000077500000000000000000000107701513515052500270220ustar00rootroot00000000000000#!/usr/bin/env python """ Test client for basedpyright + ty servers. Tests multi-server completion support. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test multi-server completions with real servers.""" client = await LspTestEndpoint.create() # Initialize with completion capabilities completion_caps = { 'textDocument': { 'completion': { 'dynamicRegistration': False, 'completionItem': { 'snippetSupport': True, 'deprecatedSupport': True, 'resolveSupport': { 'properties': ['documentation', 'details', 'additionalTextEdits'] }, 'tagSupport': {'valueSet': [1]}, 'insertReplaceSupport': True, }, 'contextSupport': True, } } } init_response = await client.initialize(capabilities=completion_caps) # Verify completionProvider is present capabilities = init_response.get('result', {}).get('capabilities', {}) assert capabilities.get('completionProvider'), "Expected completionProvider in merged capabilities" log("client", f"Got completionProvider: {capabilities.get('completionProvider')}") # Open a test document await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'version': 0, 'languageId': 'python', 'text': 'import sys\n\nsys.' } }) # Request completions at position after "sys." req_id = await client.request('textDocument/completion', { 'textDocument': {'uri': 'file:///tmp/test.py'}, 'position': {'line': 2, 'character': 4}, 'context': {'triggerKind': 2, 'triggerCharacter': '.'} }) # Read completion response comp_response = await client.read_response(req_id) result = comp_response['result'] items = result.get('items', []) log("client", f"Got {len(items)} completion items") assert len(items) > 0, "Expected at least one completion item" # Check if items have data fields (should be stashed) items_with_data = [item for item in items if 'data' in item] log("client", f"Found {len(items_with_data)} items with data fields") # Find an item without detail to resolve (basedpyright items lack detail) probe = next((item for item in items if 'data' in item and 'detail' not in item), None) assert probe is not None, "Expected to find at least one item with data but no detail" log("client", f"Found item to resolve: {probe['label']}") # Send completionItem/resolve request resolve_id = await client.request('completionItem/resolve', probe) # Read resolve response resolve_response = await client.read_response(resolve_id) resolved_item = resolve_response['result'] log("client", f"Resolved item: {resolved_item['label']}") # Check that the resolved item now has detail or documentation assert resolved_item.get('detail') or resolved_item.get('documentation'), \ f"Expected detail or documentation in resolved item, got: {resolved_item}" log("client", "Successfully resolved item with additional info") # Test '[' trigger character (only basedpyright supports this) await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test2.py', 'version': 0, 'languageId': 'python', 'text': 'x = {"result" = 42}\nx[\n' } }) # Request completions with '[' trigger bracket_id = await client.request('textDocument/completion', { 'textDocument': {'uri': 'file:///tmp/test2.py'}, 'position': {'line': 1, 'character': 2}, 'context': {'triggerKind': 2, 'triggerCharacter': '['} }) # Read response bracket_response = await client.read_response(bracket_id) bracket_items = bracket_response['result'].get('items', []) log("client", f"Got {len(bracket_items)} items for '[' trigger") # Should only get items from basedpyright (ty doesn't support '[') # FIXME: we verify it is indeed so from the logs, but # unfortunately, there's not much I can assert here. Also # basedpyright in this test answers with a bucketload of # irrelevant completions, but in the same environment with a real # client it responds with just one completion. Investigate this. await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/basedpyright+ty-completion-aggregation/run.sh000077500000000000000000000005441513515052500263300ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required LSP servers are available if ! command -v basedpyright-langserver >/dev/null 2>&1 || \ ! command -v ty >/dev/null 2>&1; then echo "Required LSP servers not found, skipping test" >&2 exit 77 fi ../yoyo.sh ./client.py --rass-- \ -- basedpyright-langserver --stdio \ -- ty server rassumfrassum-0.3.3/test/basic/000077500000000000000000000000001513515052500164725ustar00rootroot00000000000000rassumfrassum-0.3.3/test/basic/client.py000077500000000000000000000031531513515052500203270ustar00rootroot00000000000000#!/usr/bin/env python """ A more complete test client that exercises various LSP messages. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send a sequence of LSP messages.""" client = await LspTestEndpoint.create() init_response = await client.initialize() # Verify merged serverInfo result = init_response['result'] assert 'serverInfo' in result, f"Expected 'serverInfo' in result: {result}" server_info = result['serverInfo'] # Primary server should always come first assert server_info.get('name') == 's1+s2', \ f"Expected merged name 's1+s2', got '{server_info.get('name')}'" log("client", f"Verified merged server name: {server_info['name']}") await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'languageId': 'python', 'version': 1, 'text': 'print("hello")\n' } }) payload = await client.read_notification('textDocument/publishDiagnostics') log("client", f"Got diagnostics {payload}") # Hover request req_id = await client.request('textDocument/hover', { 'textDocument': {'uri': 'file:///tmp/test.py'}, 'position': {'line': 0, 'character': 0} }) hover_response = await client.read_response(req_id) assert 'result' in hover_response or 'error' in hover_response, \ f"Expected 'result' or 'error' in hover response: {hover_response}" log("client", f"Got hover response {hover_response}") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/basic/run.sh000077500000000000000000000002231513515052500176320ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 rassumfrassum-0.3.3/test/basic/server.py000077500000000000000000000016001513515052500203520ustar00rootroot00000000000000#!/usr/bin/env python """Server for basic test""" import argparse from rassumfrassum.json import write_message_sync from rassumfrassum.test2 import run_toy_server, make_diagnostic parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) args = parser.parse_args() def handle_didopen(params): text_doc = params.get('textDocument', {}) uri = text_doc.get('uri', 'file:///unknown') write_message_sync({ 'jsonrpc': '2.0', 'method': 'textDocument/publishDiagnostics', 'params': { 'uri': uri, 'diagnostics': [ make_diagnostic(0, 0, 5, 1, f'An example error from {args.name}'), make_diagnostic(0, 7, 12, 2, f'An example warning from {args.name}') ] } }) run_toy_server( name=args.name, notification_handlers={'textDocument/didOpen': handle_didopen} ) rassumfrassum-0.3.3/test/be-careful-when-merging-dianostics/000077500000000000000000000000001513515052500241415ustar00rootroot00000000000000rassumfrassum-0.3.3/test/be-careful-when-merging-dianostics/client.py000077500000000000000000000066101513515052500257770ustar00rootroot00000000000000#!/usr/bin/env python """ Test that didChange on one file doesn't clear diagnostics for other files. Regression test for bug where didChange cleanup deleted ALL dispatched aggregations, causing other files' diagnostics to be reset to empty. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log, scaled_timeout async def main(): """Test that diagnostics persist for unchanged files.""" client = await LspTestEndpoint.create() # Initialize with workspace support so ruff tracks files properly init_caps = { 'workspace': { 'workspaceFolders': True, } } _ = await client.initialize(capabilities=init_caps, rootUri='file:///tmp') # Open file A with a typo await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/fileA.py', 'version': 0, 'languageId': 'python', 'text': '# comment with typo: speling\nBLA=str;\n' } }) # Open file B with a typo await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/fileB.py', 'version': 0, 'languageId': 'python', 'text': '# comment with typo: speling\n' } }) # Collect initial diagnostics diagnostics_by_uri = {} async def collect_diags(): """Collect diagnostic notifications.""" while payload := await client.read_notification('textDocument/publishDiagnostics'): uri = payload['uri'] diags = payload.get('diagnostics', []) diagnostics_by_uri[uri] = diags log("client", f"Got {len(diags)} diagnostic(s) for {uri}") try: await asyncio.wait_for(collect_diags(), timeout=scaled_timeout(1.5)) except asyncio.TimeoutError: pass # Check we got initial diagnostics for fileB a_before = diagnostics_by_uri.get('file:///tmp/fileA.py', []) b_before = diagnostics_by_uri.get('file:///tmp/fileB.py', []) assert len(a_before) > 0, "fileA should have initial diagnostics!" assert len(b_before) > 0, "fileB should have initial diagnostics!" # Now change fileA await client.notify('textDocument/didChange', { 'textDocument': { 'uri': 'file:///tmp/fileA.py', 'version': 1 }, 'contentChanges': [{'text': '# changed comment, still has typo: speling\nBLA=str;\n'}] }) await client.notify('workspace/didChangeWatchedFiles', { "changes": [ { "uri": 'file:///tmp/fileA.py', "type": 2 } ] }) # Save fileA await client.notify('textDocument/didSave', { 'textDocument': { 'uri': 'file:///tmp/fileA.py' } }) # Collect diagnostics after the change try: await asyncio.wait_for(collect_diags(), timeout=scaled_timeout(1.5)) except asyncio.TimeoutError: pass a_after = diagnostics_by_uri.get('file:///tmp/fileA.py', []) b_after = diagnostics_by_uri.get('file:///tmp/fileB.py', []) assert b_after == b_before, "fileB should have kept diagnostics!" assert len(a_after) == len(a_before), "fileA should have kept diagnostics!" # brittle, since order could be changed... assert [s['source'] for s in a_after] == [s['source'] for s in a_before] await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/be-careful-when-merging-dianostics/run.sh000077500000000000000000000006621513515052500253100ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required servers are available if ! command -v basedpyright-langserver >/dev/null 2>&1 || \ ! command -v ruff >/dev/null 2>&1 || \ ! command -v codebook-lsp >/dev/null 2>&1; then echo "Required LSP servers not found, skipping test" >&2 exit 77 fi ../yoyo.sh ./client.py --rass-- \ -- basedpyright-langserver --stdio \ -- ruff server \ -- codebook-lsp serve rassumfrassum-0.3.3/test/cancel-request/000077500000000000000000000000001513515052500203245ustar00rootroot00000000000000rassumfrassum-0.3.3/test/cancel-request/client.py000077500000000000000000000056451513515052500221710ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test client for $/cancelRequest handling. Verifies that cancelled requests don't get responses and that the cancel notification is properly forwarded to servers. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test that $/cancelRequest properly cancels requests.""" client = await LspTestEndpoint.create() # Initialize response = await client.initialize() log("client", f"Got initialize response: {response.get('result', {}).get('serverInfo', {})}") # Send a completion request that will be delayed by the servers log("client", "Sending slow completion request...") req_id = await client.request('textDocument/completion', { 'textDocument': {'uri': 'file:///tmp/test.py'}, 'position': {'line': 0, 'character': 0} }) # Give rass time to forward the request to servers before cancelling await asyncio.sleep(0.1) # Now cancel it log("client", f"Sending $/cancelRequest for request {req_id}") await client.notify('$/cancelRequest', {'id': req_id}) # Read the next two messages - should be $/yeahGotIt notifications # Using read_message() instead of read_notification() to catch any # buggy response that might slip through from rass log("client", "Waiting for first $/yeahGotIt notification...") msg1 = await client.read_message(timeout_sec=1.0) assert 'method' in msg1, f"Expected notification, got: {msg1}" assert msg1['method'] == '$/yeahGotIt', f"Expected $/yeahGotIt, got {msg1['method']}" server1 = msg1['params'].get('server') log("client", f"Got $/yeahGotIt from {server1}") log("client", "Waiting for second $/yeahGotIt notification...") msg2 = await client.read_message(timeout_sec=1.0) assert 'method' in msg2, f"Expected notification, got: {msg2}" assert msg2['method'] == '$/yeahGotIt', f"Expected $/yeahGotIt, got {msg2['method']}" server2 = msg2['params'].get('server') log("client", f"Got $/yeahGotIt from {server2}") # Verify we got one from each server servers = {server1, server2} assert servers == {'s1', 's2'}, f"Expected notifications from s1 and s2, got {servers}" # Now try to read another message - should timeout # The servers will actually respond to the completion (they're allowed to even # after receiving $/cancelRequest), but rass should block those responses log("client", "Waiting to ensure cancelled response doesn't arrive...") try: msg = await client.read_message(timeout_sec=2.5) # If we get here, we got a message when we shouldn't have raise AssertionError(f"Expected no response to cancelled request, but got: {msg}") except asyncio.TimeoutError: # This is what we expect - no message arrived log("client", "Cancelled request response was correctly blocked!") pass await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/cancel-request/run.sh000077500000000000000000000002231513515052500214640ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 rassumfrassum-0.3.3/test/cancel-request/server.py000077500000000000000000000023641513515052500222140ustar00rootroot00000000000000#!/usr/bin/env python3 """ Server that delays hover responses and acknowledges $/cancelRequest. """ import argparse import asyncio from rassumfrassum.json import write_message_sync from rassumfrassum.test2 import run_toy_server, log parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) args = parser.parse_args() async def handle_completion(msg_id, params): """Handle completion with a delay, then respond.""" log(args.name, "Got completion request, delaying 2 seconds...") await asyncio.sleep(2.0) log(args.name, "Delay done, responding to completion") return { "isIncomplete": False, "items": [] } def handle_cancel(params): """Handle $/cancelRequest notification.""" cancelled_id = params.get('id') log(args.name, f"Got $/cancelRequest for id={cancelled_id}") # Send custom notification to confirm receipt write_message_sync({ 'jsonrpc': '2.0', 'method': '$/yeahGotIt', 'params': {'server': args.name} }) run_toy_server( name=args.name, capabilities={'completionProvider': {'triggerCharacters': ['.']}}, request_handlers={'textDocument/completion': handle_completion}, notification_handlers={'$/cancelRequest': handle_cancel} ) rassumfrassum-0.3.3/test/codeaction-aggregation/000077500000000000000000000000001513515052500220065ustar00rootroot00000000000000rassumfrassum-0.3.3/test/codeaction-aggregation/client.py000077500000000000000000000036231513515052500236450ustar00rootroot00000000000000#!/usr/bin/env python """ Test that textDocument/codeAction aggregates results from all servers with codeActionProvider capability. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test that code actions are aggregated from multiple servers.""" client = await LspTestEndpoint.create() init_response = await client.initialize() result = init_response['result'] capabilities = result.get('capabilities', {}) has_code_actions = capabilities.get('codeActionProvider') log("client", f"Got initialize response with codeActionProvider={has_code_actions}") assert has_code_actions, "Expected codeActionProvider to be present in merged capabilities" # Send textDocument/codeAction request req_id = await client.request('textDocument/codeAction', { 'textDocument': {'uri': 'file:///test.py'}, 'range': { 'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 10} }, 'context': {'diagnostics': []} }) # Expect aggregated code action response response = await client.read_response(req_id) actions = response['result'] log("client", f"Got {len(actions)} code actions") # Should have 2 code actions (from s2 and s3, but not s1) assert isinstance(actions, list), f"Expected array of code actions, got: {type(actions)}" assert len(actions) == 2, f"Expected 2 code actions (from s2 and s3), got {len(actions)}: {actions}" # Verify we got actions from both s2 and s3 titles = [a['title'] for a in actions] assert 'Fix from s2' in titles, f"Expected action from s2, got titles: {titles}" assert 'Fix from s3' in titles, f"Expected action from s3, got titles: {titles}" log("client", "✓ Code actions correctly aggregated from servers with codeActionProvider") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/codeaction-aggregation/run.sh000077500000000000000000000006461513515052500231570ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # s1 (primary) does NOT have codeActionProvider # s2 (secondary) has codeActionProvider # s3 (tertiary) has codeActionProvider # Expected: codeAction request goes to s2 and s3, responses aggregated ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 --has-code-actions \ -- python ./server.py --name s3 --has-code-actions rassumfrassum-0.3.3/test/codeaction-aggregation/server.py000077500000000000000000000015661513515052500237010ustar00rootroot00000000000000#!/usr/bin/env python """ Server that provides codeActionProvider capability. """ import argparse from rassumfrassum.test2 import run_toy_server parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--has-code-actions', action='store_true', help='Whether this server provides code actions') args = parser.parse_args() # Build capabilities based on args capabilities = {'hoverProvider': True} if args.has_code_actions: capabilities['codeActionProvider'] = True def handle_code_action(msg_id, params): """Return a code action specific to this server.""" return [ { 'title': f'Fix from {args.name}', 'kind': 'quickfix' } ] run_toy_server( name=args.name, capabilities=capabilities, request_handlers={'textDocument/codeAction': handle_code_action} ) rassumfrassum-0.3.3/test/definition-aggregation/000077500000000000000000000000001513515052500220265ustar00rootroot00000000000000rassumfrassum-0.3.3/test/definition-aggregation/client.py000077500000000000000000000035121513515052500236620ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test that textDocument/definition aggregates results from servers with definitionProvider capability. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): client = await LspTestEndpoint.create() init_response = await client.initialize() result = init_response['result'] capabilities = result.get('capabilities', {}) has_definition = capabilities.get('definitionProvider') log("client", f"Got initialize response with definitionProvider={has_definition}") assert has_definition, "Expected definitionProvider to be present in merged capabilities" req_id = await client.request('textDocument/definition', { 'textDocument': {'uri': 'file:///test.py'}, 'position': {'line': 101, 'character': 5} }) response = await client.read_response(req_id) definitions = response['result'] log("client", f"Got {len(definitions)} definitions") # Should have 3 definitions: # - s1 provides no definitions # - s2 and s3 provide 1 unique definition each # - s4 and s5 provide the same exact definition assert isinstance(definitions, list), f"Expected array of definitions, got: {type(definitions)}" assert len(definitions) == 3, f"Expected 3 definitions (from s2, s3 and s4/s5), got {len(definitions)}: {definitions}" uris = [d['targetUri'] for d in definitions] assert 'file:///s2.py' in uris, f"Expected definition from s2, got uris: {uris}" assert 'file:///s3.py' in uris, f"Expected definition from s3, got uris: {uris}" assert 'file:///s4.py' in uris or 'file:///s5.py' in uris, f"Expected definition from s4/s5, got uris: {uris}" log("client", "✓ Definitions correctly aggregated from servers with definitionProvider") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/definition-aggregation/run.sh000077500000000000000000000016011513515052500231670ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # s1 does NOT have definitionProvider, won't receive definition requests # s2 has definitionProvider, returns random locations as LocationLink (list) # s3 has definitionProvider, returns random locations as Location (dict) # s4 has definitionProvider, returns const locations as LocationLink (list) # s5 has definitionProvider, returns const locations as Location (dict) # s4 and s5 return identical locations, so one gets deduplicated # Expected: request goes to s2, s3, s4, s5; result has 3 unique definitions ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 --has-definition --as-link \ -- python ./server.py --name s3 --has-definition --in-dict \ -- python ./server.py --name s4 --has-definition --as-link --const \ -- python ./server.py --name s5 --has-definition --in-dict --const rassumfrassum-0.3.3/test/definition-aggregation/server.py000077500000000000000000000034611513515052500237150ustar00rootroot00000000000000#!/usr/bin/env python3 """ Server that provides definitionProvider capability. """ import argparse import random from rassumfrassum.test2 import run_toy_server parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--has-definition', action='store_true', help='Whether this server provides definitions') parser.add_argument('--in-dict', action='store_true', help='Whether the definition location provided is contained in a dictionary') parser.add_argument('--as-link', action='store_true', help='Whether the definition location returned provided is a link') parser.add_argument('--const', action='store_true', help='Whether the definition location returned contains always the same range') args = parser.parse_args() capabilities = {} if args.has_definition: capabilities['definitionProvider'] = True def handle_find_definition(msg_id, params): startLine, startChar = 0, 0 endLine, endChar = 0, 10 if not args.const: startLine, startChar = random.randint(11, 10000), random.randint(0, 1000) endLine, endChar = startLine, startChar + random.randint(1, 1000) definition = { 'uri': f'file:///{args.name}.py', 'range': { 'start': {'line': startLine, 'character': startChar}, 'end': {'line': endLine, 'character': endChar} } } if args.as_link: definition = { 'targetUri': definition['uri'], 'targetSelectionRange': definition['range'], 'targetRange': definition['range'] } return definition if args.in_dict else [definition] run_toy_server( name=args.name, capabilities=capabilities, request_handlers={'textDocument/definition': handle_find_definition} ) rassumfrassum-0.3.3/test/drop-tardy-notification/000077500000000000000000000000001513515052500221625ustar00rootroot00000000000000rassumfrassum-0.3.3/test/drop-tardy-notification/client.py000077500000000000000000000024231513515052500240160ustar00rootroot00000000000000#!/usr/bin/env python """ Test client for too-late diagnostics scenario. s1's diagnostics arrive after the timeout and should be discarded. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send a sequence of LSP messages.""" client = await LspTestEndpoint.create() await client.initialize() await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'languageId': 'python', 'version': 1, 'text': 'print("hello")\n' } }) payload = await client.read_notification('textDocument/publishDiagnostics') diagnostics = payload.get('diagnostics', []) assert len(diagnostics) == 2, f"Expected 2 diagnostics (only from s2, s1 timed out), got {len(diagnostics)}: {diagnostics}" # Verify only s2 contributed (s1's diagnostics were too late) sources = {d.get('source') for d in diagnostics} assert 's2' in sources, f"Expected diagnostics from s2, got sources: {sources}" assert 's1' not in sources, f"Expected s1 diagnostics to be discarded, but got sources: {sources}" log("client", f"Got diagnostics only from s2 (s1 timed out)") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/drop-tardy-notification/run.sh000077500000000000000000000004311513515052500233230ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # s1: 1500ms delay (exceeds 1000ms timeout, diagnostics discarded) # s2: immediate diagnostics ../yoyo.sh ./client.py --rass-- --drop-tardy \ -- python ./server.py --name s1 --delay-diagnostics 1500 \ -- python ./server.py --name s2 rassumfrassum-0.3.3/test/drop-tardy-notification/server.py000077500000000000000000000021201513515052500240400ustar00rootroot00000000000000#!/usr/bin/env python """Server for too-late-diagnostics-from-s1 test""" import argparse import time from rassumfrassum.test2 import run_toy_server, make_diagnostic from rassumfrassum.json import write_message_sync parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--delay-diagnostics', type=int, default=0) args = parser.parse_args() def handle_didopen(params): text_doc = params.get('textDocument', {}) uri = text_doc.get('uri', 'file:///unknown') if args.delay_diagnostics > 0: time.sleep(args.delay_diagnostics / 1000.0) write_message_sync({ 'jsonrpc': '2.0', 'method': 'textDocument/publishDiagnostics', 'params': { 'uri': uri, 'diagnostics': [ make_diagnostic(0, 0, 5, 1, f'An example error from {args.name}', args.name), make_diagnostic(0, 7, 12, 2, f'An example warning from {args.name}', args.name) ] } }) run_toy_server( name=args.name, notification_handlers={'textDocument/didOpen': handle_didopen} ) rassumfrassum-0.3.3/test/drop-tardy-response/000077500000000000000000000000001513515052500213325ustar00rootroot00000000000000rassumfrassum-0.3.3/test/drop-tardy-response/client.py000077500000000000000000000021711513515052500231660ustar00rootroot00000000000000#!/usr/bin/env python """ Test client for tardy-initialize-response test. Verifies that tardy initialize responses are dropped. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test that tardy initialize responses are dropped.""" client = await LspTestEndpoint.create() # Send initialize (use helper to ensure proper capabilities) response = await client.initialize() result = response['result'] server_info = result.get('serverInfo', {}) log("client", f"Got initialize response from: {server_info.get('name', 'unknown')}") # Wait for potential tardy response from S2 # S2 delays 3000, aggregation timeout is 2500 # Wait 3200 total to ensure tardy response has arrived at rass log("client", "Waiting for potential tardy initialize response...") await asyncio.sleep(3.2) # Critical assertion: verify no duplicate initialize response await client.assert_no_message_pending(timeout_sec=0.1) log("client", "Tardy initialize response was correctly dropped!") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/drop-tardy-response/run.sh000077500000000000000000000004621513515052500224770ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # S1 (primary) responds immediately # S2 (secondary) delays initialize response by 3000ms (exceeds 2500 default timeout) ../yoyo.sh ./client.py --rass-- --drop-tardy \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 --initialize-delay 3000 rassumfrassum-0.3.3/test/drop-tardy-response/server.py000077500000000000000000000020111513515052500232070ustar00rootroot00000000000000#!/usr/bin/env python """ Server that can delay its initialize response. Used to test tardy request response dropping. """ import argparse import time from rassumfrassum.test2 import run_toy_server, log parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--initialize-delay', type=int, default=0, help='Delay in milliseconds before responding to initialize') args = parser.parse_args() def handle_initialize(msg_id, params): """Handle initialize with optional delay.""" if args.initialize_delay > 0: log(args.name, f"Delaying initialize response by {args.initialize_delay}ms") time.sleep(args.initialize_delay / 1000.0) return { 'capabilities': { 'textDocumentSync': 2, 'hoverProvider': True, }, 'serverInfo': { 'name': args.name, 'version': '1.0.0' } } run_toy_server( name=args.name, request_handlers={'initialize': handle_initialize} ) rassumfrassum-0.3.3/test/duplicate-response/000077500000000000000000000000001513515052500212175ustar00rootroot00000000000000rassumfrassum-0.3.3/test/duplicate-response/client.py000077500000000000000000000022161513515052500230530ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test client that sends a single request and checks response handling. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send a single request to test duplicate response handling.""" client = await LspTestEndpoint.create() # Initialize response = await client.initialize() log("client", f"Got initialize response: {response.get('result', {}).get('serverInfo', {})}") # Send a custom request (not LSP-specific) log("client", "Sending custom/test request...") req_id = await client.request('custom/test', {'data': 'test'}) # Read the first response msg = await client.read_response(req_id) log("client", f"Got first response: {msg}") assert msg.get('result', {}).get('response') == 1, "Expected first response" # Critical assertion: no duplicate response should arrive log("client", "Checking that no duplicate response arrives...") await client.assert_no_message_pending(timeout_sec=0.5) log("client", "No duplicate response - correct behavior!") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/duplicate-response/run.sh000077500000000000000000000001471513515052500223640ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- -- python ./server.py --name s1 rassumfrassum-0.3.3/test/duplicate-response/server.py000077500000000000000000000020321513515052500230770ustar00rootroot00000000000000#!/usr/bin/env python3 """ Server that sends duplicate responses to test response handling. """ import argparse from rassumfrassum.test2 import run_toy_server, log parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) args = parser.parse_args() def handle_custom_request(msg_id, params, send_message): """Handle custom request by sending TWO responses with the same ID.""" log(args.name, f"Got custom request id={msg_id}, sending duplicate responses") # Send first response send_message({ 'jsonrpc': '2.0', 'id': msg_id, 'result': {'response': 1, 'from': args.name} }) log(args.name, f"Sent first response for id={msg_id}") # Send second response with the SAME id send_message({ 'jsonrpc': '2.0', 'id': msg_id, 'result': {'response': 2, 'from': args.name} }) log(args.name, f"Sent second (duplicate) response for id={msg_id}") run_toy_server( name=args.name, raw_request_handlers={'custom/test': handle_custom_request} ) rassumfrassum-0.3.3/test/file-watcher-filtering/000077500000000000000000000000001513515052500217445ustar00rootroot00000000000000rassumfrassum-0.3.3/test/file-watcher-filtering/client.py000077500000000000000000000106161513515052500236030ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test that didChangeWatchedFiles is filtered based on glob patterns. This test verifies that when a file change notification is sent, rass only forwards it to servers whose watchers match the changed file's URI. """ import asyncio from pathlib import Path from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test file watcher filtering.""" client = await LspTestEndpoint.create() # Get fixture directory path fixture_dir = Path(__file__).parent / 'fixture' root_uri = fixture_dir.resolve().as_uri() # Initialize with didChangeWatchedFiles and streaming diagnostics support capabilities = { 'workspace': { 'didChangeWatchedFiles': { 'dynamicRegistration': True, 'relativePatternSupport': True, } }, 'textDocument': { '$streamingDiagnostics': { 'dynamicRegistration': False } } } await client.initialize(capabilities=capabilities, rootUri=root_uri) # Handle client/registerCapability requests from servers # Both ty and ruff will send these log("client", "Handling registerCapability requests...") for _ in range(2): # Expect 2 registerCapability requests (ty and ruff) req_id, params = await client.read_request('client/registerCapability') log("client", f"Got registerCapability: {params.get('registrations', [{}])[0].get('id')}") await client.respond(req_id, None) # Open main.py log("client", "Opening main.py") main_uri = (fixture_dir / 'main.py').resolve().as_uri() await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': main_uri, 'version': 1, 'languageId': 'python', 'text': 'def foo():\n pass\n' } }) # Collect initial diagnostics (expect exactly 2: from ty and ruff) log("client", "Collecting initial diagnostics...") await client.read_notification('$/streamDiagnostics') log("client", "Got first diagnostic for main.py") await client.read_notification('$/streamDiagnostics') log("client", "Got second diagnostic for main.py") # Now send didChangeWatchedFiles for nearby.py # This should only match ty's watchers (watching **/*, i.e., all Python files) # It should NOT match ruff's watchers (only *.toml files) log("client", "Sending didChangeWatchedFiles for nearby.py...") nearby_uri = (fixture_dir / 'nearby.py').resolve().as_uri() await client.notify('workspace/didChangeWatchedFiles', { 'changes': [ { 'uri': nearby_uri, 'type': 2 # Changed } ] }) # Try to collect diagnostics after the nearby.py change # We should NOT get diagnostics since nearby.py was correctly filtered log("client", "Checking that nearby.py was filtered...") got_diag_after_nearby = False try: diag = await asyncio.wait_for( client.read_notification('$/streamDiagnostics'), timeout=1.0 ) log("client", f"WARNING: Got unexpected diagnostic for {diag.get('uri')}") got_diag_after_nearby = True except asyncio.TimeoutError: log("client", "Timeout - no diagnostics as expected") assert not got_diag_after_nearby, "Expected no diagnostics after nearby.py change" log("client", "SUCCESS: nearby.py notification correctly filtered out") # Now send didChangeWatchedFiles for pyproject.toml # This should match ruff's watchers (*.toml files) # We expect ruff to re-analyze and send diagnostics for main.py log("client", "Sending didChangeWatchedFiles for pyproject.toml...") toml_uri = (fixture_dir / 'pyproject.toml').resolve().as_uri() await client.notify('workspace/didChangeWatchedFiles', { 'changes': [ { 'uri': toml_uri, 'type': 2 # Changed } ] }) # Wait for diagnostics from ruff for main.py log("client", "Waiting for diagnostic for main.py from ruff...") ruff_diag = await client.read_notification('$/streamDiagnostics') assert ruff_diag.get('uri') == main_uri, \ f"Expected diagnostic for main.py, got {ruff_diag.get('uri')}" log("client", "SUCCESS: Got diagnostic for main.py from ruff after toml change") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/file-watcher-filtering/fixture/000077500000000000000000000000001513515052500234325ustar00rootroot00000000000000rassumfrassum-0.3.3/test/file-watcher-filtering/fixture/main.py000066400000000000000000000000241513515052500247240ustar00rootroot00000000000000def foo(): pass rassumfrassum-0.3.3/test/file-watcher-filtering/fixture/nearby.py000066400000000000000000000000241513515052500252600ustar00rootroot00000000000000def bar(): pass rassumfrassum-0.3.3/test/file-watcher-filtering/fixture/pyproject.toml000066400000000000000000000000351513515052500263440ustar00rootroot00000000000000[tool.ruff] line-length = 88 rassumfrassum-0.3.3/test/file-watcher-filtering/run.sh000077500000000000000000000004611513515052500231100ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required LSP servers are available if ! command -v ty >/dev/null 2>&1 || \ ! command -v ruff >/dev/null 2>&1; then echo "Required LSP servers not found, skipping test" >&2 exit 77 fi ../yoyo.sh ./client.py --rass-- -- ty server -- ruff server rassumfrassum-0.3.3/test/late-diagnostics-from-s2/000077500000000000000000000000001513515052500221265ustar00rootroot00000000000000rassumfrassum-0.3.3/test/late-diagnostics-from-s2/client.py000077500000000000000000000022261513515052500237630ustar00rootroot00000000000000#!/usr/bin/env python """ Test client for late diagnostics scenario. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send a sequence of LSP messages.""" client = await LspTestEndpoint.create() await client.initialize() await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'languageId': 'python', 'version': 1, 'text': 'print("hello")\n' } }) payload = await client.read_notification('textDocument/publishDiagnostics') diagnostics = payload.get('diagnostics', []) assert len(diagnostics) == 4, f"Expected 4 diagnostics (2 from each server), got {len(diagnostics)}: {diagnostics}" # Verify both servers contributed sources = {d.get('source') for d in diagnostics} assert 's1' in sources, f"Expected diagnostics from s1, got sources: {sources}" assert 's2' in sources, f"Expected diagnostics from s2, got sources: {sources}" log("client", f"Got aggregated diagnostics from both servers") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/late-diagnostics-from-s2/run.sh000077500000000000000000000003671513515052500232770ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # s1: immediate diagnostics # s2: 500ms delay (well within 1000ms timeout) ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 --delay-diagnostics 500 rassumfrassum-0.3.3/test/late-diagnostics-from-s2/server.py000077500000000000000000000021141513515052500240070ustar00rootroot00000000000000#!/usr/bin/env python """Server for late-diagnostics-from-s2 test""" import argparse import time from rassumfrassum.test2 import run_toy_server, make_diagnostic from rassumfrassum.json import write_message_sync parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--delay-diagnostics', type=int, default=0) args = parser.parse_args() def handle_didopen(params): text_doc = params.get('textDocument', {}) uri = text_doc.get('uri', 'file:///unknown') if args.delay_diagnostics > 0: time.sleep(args.delay_diagnostics / 1000.0) write_message_sync({ 'jsonrpc': '2.0', 'method': 'textDocument/publishDiagnostics', 'params': { 'uri': uri, 'diagnostics': [ make_diagnostic(0, 0, 5, 1, f'An example error from {args.name}', args.name), make_diagnostic(0, 7, 12, 2, f'An example warning from {args.name}', args.name) ] } }) run_toy_server( name=args.name, notification_handlers={'textDocument/didOpen': handle_didopen} ) rassumfrassum-0.3.3/test/out-of-order-diagnostics/000077500000000000000000000000001513515052500222405ustar00rootroot00000000000000rassumfrassum-0.3.3/test/out-of-order-diagnostics/client.py000077500000000000000000000037261513515052500241030ustar00rootroot00000000000000#!/usr/bin/env python """ Test client for out-of-order diagnostic versions. Verifies that stale diagnostics are dropped. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test that stale diagnostics are dropped.""" client = await LspTestEndpoint.create() await client.initialize() # Send didOpen with version 1 await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'languageId': 'python', 'version': 1, 'text': 'print("hello")\n' } }) # Expect diagnostics for version 1 payload = await client.read_notification('textDocument/publishDiagnostics') assert payload.get('version') == 1 diag_count_v1 = len(payload.get('diagnostics', [])) log("client", f"Got diagnostics for version 1: {diag_count_v1} diagnostics") # Send didChange with version 2 await client.notify('textDocument/didChange', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'version': 2 }, 'contentChanges': [ {'text': 'print("hello world")\n'} ] }) # Expect diagnostics for version 2 payload = await client.read_notification('textDocument/publishDiagnostics') assert payload.get('version') == 2 diag_count_v2 = len(payload.get('diagnostics', [])) log("client", f"Got diagnostics for version 2: {diag_count_v2} diagnostics") # Wait for potential stale v1 diagnostic (should NOT arrive) # Server sends it after 300ms. Aggregation timeout is 1000ms. # Wait 1500ms to make 100% sure we didn't start a new aggregation # which has timed out for some reason. log("client", "Checking that stale v1 diagnostic was dropped...") await client.assert_no_message_pending(timeout_sec=1.5) log("client", "Stale v1 diagnostic was correctly dropped!") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/out-of-order-diagnostics/run.sh000077500000000000000000000002431513515052500234020ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 --send-stale-v1 \ -- python ./server.py --name s2 rassumfrassum-0.3.3/test/out-of-order-diagnostics/server.py000077500000000000000000000044151513515052500241270ustar00rootroot00000000000000#!/usr/bin/env python """ Server that sends out-of-order diagnostics to test version staleness detection. After sending v2 diagnostics, s1 will send a stale v1 diagnostic. """ import argparse import threading import time from rassumfrassum.test2 import run_toy_server, make_diagnostic, log from rassumfrassum.json import write_message_sync parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--send-stale-v1', action='store_true', help='Send stale v1 after v2') args = parser.parse_args() def send_diagnostics(uri, version): """Send diagnostics for a specific version.""" write_message_sync({ 'jsonrpc': '2.0', 'method': 'textDocument/publishDiagnostics', 'params': { 'uri': uri, 'version': version, 'diagnostics': [ make_diagnostic(0, 0, 5, 1, f'Error from {args.name} v{version}'), make_diagnostic(0, 7, 12, 2, f'Warning from {args.name} v{version}') ] } }) log(args.name, f"sent diagnostics for {uri} version {version}") def send_stale_diagnostic_later(uri): """Send a stale v1 diagnostic after a delay.""" time.sleep(0.3) # Wait 300ms log(args.name, f"sending STALE v1 diagnostic for {uri}") send_diagnostics(uri, 1) def on_didchange_handler(uri, text_doc): """Handle didChange - send current version, and maybe send stale v1.""" version = text_doc.get('version', 0) send_diagnostics(uri, version) # If this is v2 and we're configured to send stale, spawn thread to send v1 later if version == 2 and args.send_stale_v1: thread = threading.Thread(target=send_stale_diagnostic_later, args=(uri,)) thread.daemon = True thread.start() def handle_didopen(params): text_doc = params.get('textDocument', {}) uri = text_doc.get('uri', 'file:///unknown') version = text_doc.get('version', 0) send_diagnostics(uri, version) def handle_didchange(params): text_doc = params.get('textDocument', {}) uri = text_doc.get('uri', 'file:///unknown') on_didchange_handler(uri, text_doc) run_toy_server( name=args.name, notification_handlers={ 'textDocument/didOpen': handle_didopen, 'textDocument/didChange': handle_didchange } ) rassumfrassum-0.3.3/test/parallel-server-requests/000077500000000000000000000000001513515052500223625ustar00rootroot00000000000000rassumfrassum-0.3.3/test/parallel-server-requests/client.py000077500000000000000000000051211513515052500242140ustar00rootroot00000000000000#!/usr/bin/env python3 """ Client that sends three back-to-back textDocument/diagnostic requests. Tests that server handles them in parallel (should take ~5 seconds) vs sequentially (would take ~15 seconds). """ import asyncio import time from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send three textDocument/diagnostic requests back-to-back.""" client = await LspTestEndpoint.create() # Initialize await client.initialize() # Open a document await client.notify('textDocument/didOpen', { 'textDocument': { 'uri': 'file:///tmp/test.py', 'languageId': 'python', 'version': 1, 'text': 'print("hello")\n' } }) # Send three slow requests back-to-back log("client", "Sending 3 dummy/slowRequest requests") start_time = time.time() req_id1 = await client.request('dummy/slowRequest', {}) req_id2 = await client.request('dummy/slowRequest', {}) req_id3 = await client.request('dummy/slowRequest', {}) log("client", f"Sent all 3 requests at t={time.time() - start_time:.2f}s") # Wait for all three responses resp1 = await client.read_response(req_id1) log("client", f"Got response 1 at t={time.time() - start_time:.2f}s") resp2 = await client.read_response(req_id2) log("client", f"Got response 2 at t={time.time() - start_time:.2f}s") resp3 = await client.read_response(req_id3) elapsed = time.time() - start_time log("client", f"Got response 3 at t={elapsed:.2f}s") # Check that all responses are valid assert 'result' in resp1, f"Expected result in response 1: {resp1}" assert 'result' in resp2, f"Expected result in response 2: {resp2}" assert 'result' in resp3, f"Expected result in response 3: {resp3}" # The key assertion: Since the server handles requests with async handlers, # they execute in parallel. All three 5-second requests complete in ~5 seconds total. log("client", f"Total elapsed time: {elapsed:.2f}s") # Allow some tolerance for timing if elapsed < 4: log("client", f"ERROR: Took {elapsed:.2f}s - too fast, something's wrong!") raise AssertionError(f"Expected ~5s (parallel), but took {elapsed:.2f}s") elif elapsed > 7: log("client", f"ERROR: Took {elapsed:.2f}s - too slow, not parallel enough!") raise AssertionError(f"Expected ~5s (parallel), but took {elapsed:.2f}s") else: log("client", f"SUCCESS: Took {elapsed:.2f}s - responses handled in parallel as expected") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/parallel-server-requests/run.sh000077500000000000000000000001351513515052500235240ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- -- python ./server.py rassumfrassum-0.3.3/test/parallel-server-requests/server.py000077500000000000000000000010631513515052500242450ustar00rootroot00000000000000#!/usr/bin/env python3 """Server for parallel-server-requests test""" import asyncio from rassumfrassum.test2 import run_toy_server, log async def handle_slow_request(msg_id, params): """Handle dummy/slowRequest with a 5-second delay.""" log('async-server', f"Starting to handle dummy/slowRequest id={msg_id}") await asyncio.sleep(5) log('async-server', f"Finished handling dummy/slowRequest id={msg_id}") return {'result': 'done'} run_toy_server( name='async-server', request_handlers={'dummy/slowRequest': handle_slow_request} ) rassumfrassum-0.3.3/test/python-preset/000077500000000000000000000000001513515052500202325ustar00rootroot00000000000000rassumfrassum-0.3.3/test/python-preset/client.py000077500000000000000000000011741513515052500220700ustar00rootroot00000000000000#!/usr/bin/env python """ Simple test client for real LSP servers. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint async def main(): """Send initialize and shutdown to real servers.""" client = await LspTestEndpoint.create() # Initialize init_response = await client.initialize() # Just verify we got a response with capabilities result = init_response.get('result', {}) capabilities = result.get('capabilities', {}) assert capabilities, "Expected capabilities in initialize response" # Shutdown await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/python-preset/run.sh000077500000000000000000000004541513515052500214000ustar00rootroot00000000000000#!/bin/bash set -e set -o pipefail cd $(dirname "$0") # Check if required LSP servers are available if ! command -v ty >/dev/null 2>&1 || \ ! command -v ruff >/dev/null 2>&1; then echo "Required LSP servers not found, skipping test" >&2 exit 77 fi ../yoyo.sh ./client.py --rass-- python rassumfrassum-0.3.3/test/rass-originated-request/000077500000000000000000000000001513515052500221725ustar00rootroot00000000000000rassumfrassum-0.3.3/test/rass-originated-request/client.py000077500000000000000000000021441513515052500240260ustar00rootroot00000000000000#!/usr/bin/env python3 """ Client for rass-originated-request test. Tests that LspLogic can use request_server to make independent requests to servers. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test rass-originated requests.""" client = await LspTestEndpoint.create() # Initialize await client.initialize() # Send the notification that will trigger the custom logic log("client", "Sending dummy_client_notif") await client.notify('dummy_client_notif', {}) # Wait for the notification from the custom logic log("client", "Waiting for dummy_server_notif") response = await client.read_notification('dummy_server_notif') log("client", f"Got dummy_server_notif: {response}") # Verify the response assert 'value' in response, f"Expected 'value' in response: {response}" assert response['value'] == 42, f"Expected value=42, got {response['value']}" log("client", "SUCCESS: Received correct response from rass-originated request") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/rass-originated-request/custom_logic.py000066400000000000000000000022021513515052500252270ustar00rootroot00000000000000"""Custom LSP logic for testing rass-originated requests.""" from rassumfrassum.frassum import LspLogic from rassumfrassum.json import JSON class CustomLogic(LspLogic): """Custom logic that makes requests to servers independently.""" async def on_client_notification(self, method: str, params: JSON) -> None: """Intercept dummy_client_notif and make a request to the server.""" await super().on_client_notification(method, params) if method == 'dummy_client_notif': # Make a request to the primary server is_error, response = await self.request_server( self.primary, 'dummy_method', {} ) # Verify the response if not is_error and response == 42: # Send notification back to client await self.notify_client('dummy_server_notif', {'value': response}) else: # Send error notification await self.notify_client( 'dummy_server_notif', {'error': 'Unexpected response', 'is_error': is_error, 'response': response} ) rassumfrassum-0.3.3/test/rass-originated-request/run.sh000077500000000000000000000004141513515052500233340ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Add current test directory to PYTHONPATH # so rass can import the custom_logic module export PYTHONPATH="$(pwd):${PYTHONPATH}" ../yoyo.sh ./client.py --rass-- --logic-class custom_logic.CustomLogic \ -- python ./server.py rassumfrassum-0.3.3/test/rass-originated-request/server.py000077500000000000000000000005111513515052500240520ustar00rootroot00000000000000#!/usr/bin/env python3 """Server for rass-originated-request test.""" from rassumfrassum.test2 import run_toy_server def handle_dummy_method(msg_id, params): """Handle dummy_method request and return 42.""" return 42 run_toy_server( name='test-server', request_handlers={'dummy_method': handle_dummy_method} ) rassumfrassum-0.3.3/test/rename-routing/000077500000000000000000000000001513515052500203455ustar00rootroot00000000000000rassumfrassum-0.3.3/test/rename-routing/client.py000077500000000000000000000033171513515052500222040ustar00rootroot00000000000000#!/usr/bin/env python """ Test that textDocument/rename routes to the first server with renameProvider. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test that rename routes to first server with renameProvider.""" client = await LspTestEndpoint.create() init_response = await client.initialize() result = init_response['result'] capabilities = result.get('capabilities', {}) has_rename = capabilities.get('renameProvider') log("client", f"Got initialize response with renameProvider={has_rename}") assert has_rename, "Expected renameProvider to be present in merged capabilities" # Send textDocument/rename request req_id = await client.request('textDocument/rename', { 'textDocument': {'uri': 'file:///test.py'}, 'position': {'line': 0, 'character': 0}, 'newName': 'newName' }) # Expect rename response from ONLY s2 (first server with renameProvider) response = await client.read_response(req_id) workspace_edit = response['result'] log("client", f"Got rename response: {workspace_edit}") # Should be from s2 only (no aggregation, early termination) changes = workspace_edit.get('changes', {}) assert 'file:///test.py' in changes, f"Expected changes for file:///test.py" edits = changes['file:///test.py'] assert len(edits) == 1, f"Expected 1 edit (from s2 only), got {len(edits)}: {edits}" new_text = edits[0]['newText'] assert new_text == 'renamed_by_s2', f"Expected rename from s2, got: {new_text}" log("client", "✓ Rename correctly routed to first server with renameProvider (s2)") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/rename-routing/run.sh000077500000000000000000000006251513515052500215130ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # s1 (primary) does NOT have renameProvider # s2 (secondary) has renameProvider # s3 (tertiary) has renameProvider # Expected: rename request goes ONLY to s2 (first with capability), NOT to s3 ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 --has-rename \ -- python ./server.py --name s3 --has-rename rassumfrassum-0.3.3/test/rename-routing/server.py000077500000000000000000000021041513515052500222250ustar00rootroot00000000000000#!/usr/bin/env python """ Server that provides renameProvider capability. """ import argparse from rassumfrassum.test2 import run_toy_server parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--has-rename', action='store_true', help='Whether this server provides rename') args = parser.parse_args() # Build capabilities based on args capabilities = {'hoverProvider': True} if args.has_rename: capabilities['renameProvider'] = True def handle_rename(msg_id, params): """Return a workspace edit from this server.""" return { 'changes': { 'file:///test.py': [ { 'range': { 'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 10} }, 'newText': f'renamed_by_{args.name}' } ] } } run_toy_server( name=args.name, capabilities=capabilities, request_handlers={'textDocument/rename': handle_rename} ) rassumfrassum-0.3.3/test/run-all.sh000077500000000000000000000102351513515052500173230ustar00rootroot00000000000000#!/bin/bash # Run all tests in parallel and report results TIMEOUT_SCALE=${TIMEOUT_SCALE:-1.0} TIMEOUT=$(awk "BEGIN {printf \"%.1f\", ${TIMEOUT:-10} * $TIMEOUT_SCALE}") BATCH_SIZE=${BATCH_SIZE:-8} # Colorize helper (set to false to disable colors) USE_COLOR=true colorize() { if [ "$USE_COLOR" = true ]; then case "$2" in green) echo -e "\033[32m$1\033[0m" ;; yellow) echo -e "\033[33m$1\033[0m" ;; red) echo -e "\033[31m$1\033[0m" ;; *) echo "$1" ;; esac else echo "$1" fi } export -f colorize export USE_COLOR # Find all test directories (those containing run.sh) TEST_DIRS=$(find test -mindepth 2 -maxdepth 2 -name "run.sh" -type f -executable | sed 's|/run.sh$||' | sort) if [ -z "$TEST_DIRS" ]; then echo "No tests found" exit 1 fi # Create temp dir for outputs TMPDIR=$(mktemp -d) trap "rm -rf '$TMPDIR'" EXIT # Convert TEST_DIRS to array for batch processing TEST_ARRAY=($TEST_DIRS) TOTAL_TESTS=${#TEST_ARRAY[@]} # Launch tests in batches for ((i=0; i&1) rc=$? end=$(date +%s%N) elapsed_ns=$((end - start)) elapsed=$(awk "BEGIN {printf \"%.3f\", $elapsed_ns / 1000000000}") # Determine status with colors case $rc in 0) status=$(colorize "PASSED" green) ;; 77) status=$(colorize "SKIPPED" yellow) ;; 124) status=$(colorize "TIMED OUT" red) ;; *) status=$(colorize "FAILED" red) ;; esac # Save results echo "$rc" > "$TMPDIR/$n.rc" echo "$elapsed" > "$TMPDIR/$n.time" echo "$output" > "$TMPDIR/$n.output" # Print completion message printf "%s: %s (%ss)\n" "$status" "$n" "$elapsed" ) & PIDS+=($!) done # Wait for current batch to finish for pid in "${PIDS[@]}"; do wait $pid done done echo # Collect results PASSED=0 FAILED=0 TIMEDOUT=0 SKIPPED=0 FAILED_TESTS=() TIMEDOUT_TESTS=() SKIPPED_TESTS=() for d in $TEST_DIRS; do n=$(basename "$d") rc=$(cat "$TMPDIR/$n.rc") case $rc in 0) PASSED=$((PASSED + 1)) ;; 77) SKIPPED=$((SKIPPED + 1)) SKIPPED_TESTS+=("$n") ;; 124) TIMEDOUT=$((TIMEDOUT + 1)) TIMEDOUT_TESTS+=("$n") ;; *) FAILED=$((FAILED + 1)) FAILED_TESTS+=("$n") ;; esac done # Print outputs for non-passing tests if [ ${#FAILED_TESTS[@]} -gt 0 ]; then for test in "${FAILED_TESTS[@]}"; do echo "--- Output from $test ---" cat "$TMPDIR/$test.output" echo "--- End of $test ---" echo done fi if [ ${#TIMEDOUT_TESTS[@]} -gt 0 ]; then for test in "${TIMEDOUT_TESTS[@]}"; do echo "--- Output from $test ---" cat "$TMPDIR/$test.output" echo "--- End of $test ---" echo done fi if [ ${#SKIPPED_TESTS[@]} -gt 0 ]; then for test in "${SKIPPED_TESTS[@]}"; do echo "--- Output from $test ---" cat "$TMPDIR/$test.output" echo "--- End of $test ---" echo done fi echo "$PASSED passed, $FAILED failed, $SKIPPED skipped, $TIMEDOUT timed out" if [ $FAILED -gt 0 ]; then echo "Failed tests:" for test in "${FAILED_TESTS[@]}"; do echo " - $test" done fi if [ $TIMEDOUT -gt 0 ]; then echo "Timed-out tests:" for test in "${TIMEDOUT_TESTS[@]}"; do echo " - $test" done fi if [ $SKIPPED -gt 0 ]; then echo "Skipped tests:" for test in "${SKIPPED_TESTS[@]}"; do echo " - $test" done fi rc=0 if [ $FAILED -gt 0 ] || [ $TIMEDOUT -gt 0 ]; then rc=1 fi exit $rc rassumfrassum-0.3.3/test/server-crash/000077500000000000000000000000001513515052500200155ustar00rootroot00000000000000rassumfrassum-0.3.3/test/server-crash/client.py000077500000000000000000000014051513515052500216500ustar00rootroot00000000000000#!/usr/bin/env python """ Test client that expects rass to exit when server crashes. """ import asyncio import sys from rassumfrassum.test2 import LspTestEndpoint, log from rassumfrassum.json import read_message async def main(): """Send initialize and initialized, then expect connection to die.""" client = await LspTestEndpoint.create() await client.initialize() # After initialized, one of the servers will crash # We expect rass to exit, so we should get EOF msg = await read_message(client.reader) if msg is not None: log("client", f"ERROR: Expected EOF but got message: {msg}") sys.exit(1) log("client", "Got EOF as expected - rass exited after server crash") if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/server-crash/run.sh000077500000000000000000000007661513515052500211710ustar00rootroot00000000000000#!/bin/bash cd $(dirname "$0") # Server s2 will crash after initialization # We expect rass to exit with error code 1 set +e ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 \ -- python ./server.py --name s2 --crash-after-init EXIT_CODE=$? set -e # Check if rass exited with error (expected behavior) if [ $EXIT_CODE -eq 1 ]; then exit 0 # Test passed - rass exited with error as expected else echo "Test failed: Expected exit code 1, got $EXIT_CODE" exit 1 fi rassumfrassum-0.3.3/test/server-crash/server.py000077500000000000000000000010361513515052500217000ustar00rootroot00000000000000#!/usr/bin/env python """Server for server-crash test""" import argparse import sys from rassumfrassum.test2 import run_toy_server, log parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--crash-after-init', action='store_true') args = parser.parse_args() def handle_initialized(params): if args.crash_after_init: log(args.name, "Crashing as requested") sys.exit(42) run_toy_server( name=args.name, notification_handlers={'initialized': handle_initialized} ) rassumfrassum-0.3.3/test/server-request/000077500000000000000000000000001513515052500204055ustar00rootroot00000000000000rassumfrassum-0.3.3/test/server-request/client.py000077500000000000000000000020621513515052500222400ustar00rootroot00000000000000#!/usr/bin/env python """ Test client that handles server requests. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send a sequence of LSP messages and handle server requests.""" client = await LspTestEndpoint.create() await client.initialize() # After initialized, we expect server requests for workspace/configuration # First, read both request IDs request_ids = [] for i in range(2): id, payload = await client.read_request('workspace/configuration') log("client", f"Got server request: id={id} params={payload}") request_ids.append(id) # Then respond to both requests for id in request_ids: await client.respond(id, [{'pythonPath': '/usr/bin/python3'}]) log("client", f"Responding to server request id={id}") for i in range(2): _msg = await client.read_notification('custom/requestResponseOk') log("client", f"Got success notification {i+1}") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/server-request/run.sh000077500000000000000000000003071513515052500215500ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 --send-request-after-init \ -- python ./server.py --name s2 --send-request-after-init rassumfrassum-0.3.3/test/server-request/server.py000077500000000000000000000014441513515052500222730ustar00rootroot00000000000000#!/usr/bin/env python """Server for server-request test""" import argparse from rassumfrassum.test2 import run_toy_server, log from rassumfrassum.json import write_message_sync parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--send-request-after-init', action='store_true') args = parser.parse_args() def handle_initialized(params): if args.send_request_after_init: log(args.name, "Sending request to client: workspace/configuration") write_message_sync({ 'jsonrpc': '2.0', 'id': 999, 'method': 'workspace/configuration', 'params': {'items': [{'section': 'python'}]} }) run_toy_server( name=args.name, notification_handlers={'initialized': handle_initialized} ) rassumfrassum-0.3.3/test/serverinfo-merge/000077500000000000000000000000001513515052500206705ustar00rootroot00000000000000rassumfrassum-0.3.3/test/serverinfo-merge/client.py000077500000000000000000000030111513515052500225160ustar00rootroot00000000000000#!/usr/bin/env python """ Test client that verifies serverInfo merging in initialize response. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Send initialize and verify merged serverInfo.""" client = await LspTestEndpoint.create() # Send initialize init_response = await client.initialize() result = init_response['result'] # Check capabilities exist assert 'capabilities' in result, f"Expected 'capabilities' in result: {result}" # Check serverInfo assert 'serverInfo' in result, f"Expected 'serverInfo' in result: {result}" server_info = result['serverInfo'] # Verify merged name (should be "s1+s2") expected_name = "s1+s2" assert 'name' in server_info, f"Expected 'name' in serverInfo: {server_info}" assert server_info['name'] == expected_name, \ f"Expected name '{expected_name}', got '{server_info['name']}'" log("client", f"Verified merged name: {server_info['name']}") # Verify merged version (should be "1.0.0,2.0.0") expected_version = "1.0.0,2.0.0" assert 'version' in server_info, f"Expected 'version' in serverInfo: {server_info}" assert server_info['version'] == expected_version, \ f"Expected version '{expected_version}', got '{server_info['version']}'" log("client", f"Verified merged version: {server_info['version']}") log("client", "Got initialize response with correct merged serverInfo") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/serverinfo-merge/run.sh000077500000000000000000000003031513515052500220270ustar00rootroot00000000000000#!/bin/bash set -e set -o pipefail cd $(dirname "$0") ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 --version 1.0.0 \ -- python ./server.py --name s2 --version 2.0.0 rassumfrassum-0.3.3/test/serverinfo-merge/server.py000077500000000000000000000005221513515052500225520ustar00rootroot00000000000000#!/usr/bin/env python """Server for serverinfo-merge test""" import argparse from rassumfrassum.test2 import run_toy_server parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--version', default='1.0.0') args = parser.parse_args() run_toy_server(name=args.name, version=args.version) rassumfrassum-0.3.3/test/textdocumentsync-full-wins/000077500000000000000000000000001513515052500227475ustar00rootroot00000000000000rassumfrassum-0.3.3/test/textdocumentsync-full-wins/client.py000077500000000000000000000021461513515052500246050ustar00rootroot00000000000000#!/usr/bin/env python """ Test that textDocumentSync=1 (Full) wins over textDocumentSync=2 (Incremental). Even if the primary server reports Incremental, if any secondary server reports Full, the merged capability should be Full. """ import asyncio from rassumfrassum.test2 import LspTestEndpoint, log async def main(): """Test that textDocumentSync=1 wins.""" client = await LspTestEndpoint.create() init_response = await client.initialize() result = init_response['result'] capabilities = result.get('capabilities', {}) text_doc_sync = capabilities.get('textDocumentSync') log("client", f"Got initialize response with textDocumentSync={text_doc_sync}") # The key assertion: textDocumentSync should be 1 (Full) # because s1 (secondary) has textDocumentSync=1, even though s2 (primary) has textDocumentSync=2 assert text_doc_sync == 1, \ f"Expected textDocumentSync=1 (Full) to win, but got: {text_doc_sync}" log("client", "✓ textDocumentSync=1 correctly won over textDocumentSync=2") await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/textdocumentsync-full-wins/run.sh000077500000000000000000000005311513515052500241110ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # s1 (primary) has textDocumentSync=2 (Incremental) # s2 (secondary) has textDocumentSync=1 (Full) # The bug: merged result will be 2, but should be 1 ../yoyo.sh ./client.py --rass-- \ -- python ./server.py --name s1 --text-document-sync 2 \ -- python ./server.py --name s2 --text-document-sync 1 rassumfrassum-0.3.3/test/textdocumentsync-full-wins/server.py000077500000000000000000000010471513515052500246340ustar00rootroot00000000000000#!/usr/bin/env python """ Server that reports configurable textDocumentSync capability. """ import argparse from rassumfrassum.test2 import run_toy_server parser = argparse.ArgumentParser() parser.add_argument('--name', required=True) parser.add_argument('--text-document-sync', type=int, default=2, help='textDocumentSync value: 1=Full, 2=Incremental') args = parser.parse_args() run_toy_server( name=args.name, capabilities={ 'textDocumentSync': args.text_document_sync, 'hoverProvider': True, } ) rassumfrassum-0.3.3/test/ts-preset-streaming/000077500000000000000000000000001513515052500213265ustar00rootroot00000000000000rassumfrassum-0.3.3/test/ts-preset-streaming/client.py000077500000000000000000000075461513515052500231750ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test client for TypeScript preset with ESLint in streaming mode. """ import asyncio import os from rassumfrassum.test2 import LspTestEndpoint, log from rassumfrassum.json import write_message async def main(): """Test ESLint diagnostics via TypeScript preset in streaming mode.""" client = await LspTestEndpoint.create() # Set working directory to the test project fixture test_dir = os.path.dirname(os.path.abspath(__file__)) test_project = os.path.join(test_dir, "fixture") os.chdir(test_project) # Initialize with workspace configuration capability capabilities = { 'workspace': { 'configuration': True, }, 'textDocument': { 'publishDiagnostics': { 'relatedInformation': True, 'tagSupport': {'valueSet': [1, 2]}, 'versionSupport': False, }, }, } init_response = await client.initialize( capabilities=capabilities, rootUri=f"file://{test_project}" ) # Verify we got a response with capabilities result = init_response.get('result', {}) server_caps = result.get('capabilities', {}) assert server_caps, "Expected capabilities in initialize response" log(client.name, f"Server capabilities: {list(server_caps.keys())}") # Open the TypeScript file file_path = os.path.join(test_project, "src/index.ts") with open(file_path, 'r') as f: file_content = f.read() file_uri = f"file://{file_path}" log(client.name, f"Opening {file_uri}") await client.notify( 'textDocument/didOpen', { 'textDocument': { 'uri': file_uri, 'languageId': 'typescript', 'version': 1, 'text': file_content, } }, ) # Handle configuration requests (servers need these before sending diagnostics) # First configuration request (likely from eslint-language-server) sreq_id1, rparams1 = await client.read_request('workspace/configuration') items1 = rparams1.get('items', []) log(client.name, f"server req #{sreq_id1} for {len(items1)} item(s)") await write_message( client.writer, {'jsonrpc': '2.0', 'id': sreq_id1, 'result': [None] * len(items1)}, ) log(client.name, f"Sent null configuration response #{sreq_id1}") # Second configuration request (likely from typescript-language-server) sreq_id2, rparams2 = await client.read_request('workspace/configuration') items2 = rparams2.get('items', []) log(client.name, f"server req #{sreq_id2} for {len(items2)} item(s)") await write_message( client.writer, {'jsonrpc': '2.0', 'id': sreq_id2, 'result': [None] * len(items2)}, ) log(client.name, f"Sent null configuration response #{sreq_id2}") # In streaming mode, rass will automatically pull diagnostics and push them # Keep reading until we have diagnostics from both typescript and eslint log(client.name, "Reading diagnostic pushes...") sources_seen = set() all_diagnostics = [] while len(sources_seen) < 2: push = await client.read_notification('$/streamDiagnostics') diags = push.get('diagnostics', []) if diags: source = diags[0].get('source', 'unknown') sources_seen.add(source) all_diagnostics.extend(diags) log(client.name, f"Got {len(diags)} diagnostic(s) from {source}") # Check that at least one diagnostic is from eslint eslint_diags = [d for d in all_diagnostics if d.get('source') == 'eslint'] log(client.name, f"Got {len(eslint_diags)} ESLint diagnostic(s) total") assert len(eslint_diags) > 0, "Expected at least one ESLint diagnostic" log(client.name, "OK! Got diagnostics from both servers in streaming mode!") # Shutdown await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/000077500000000000000000000000001513515052500230145ustar00rootroot00000000000000rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/.eslintrc.cjs000066400000000000000000000011021513515052500254100ustar00rootroot00000000000000module.exports = { env: { node: true }, parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", ], ignorePatterns: [".eslintrc.js", "src/**/*.test.ts", "@types/*"], parserOptions: { sourceType: "module", ecmaFeatures: { modules: true, }, project: ["./tsconfig.json"], }, rules: { "@typescript-eslint/require-await": 0, "no-await-in-loop": 2, }, settings: { jsdoc: { mode: "typescript" } }, }; rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/.gitignore000066400000000000000000000000401513515052500247760ustar00rootroot00000000000000node_modules/ package-lock.json rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/package.json000066400000000000000000000006271513515052500253070ustar00rootroot00000000000000{ "name": "module-starter", "version": "0.0.1", "description": "module template", "main": "lib/src/index.js", "scripts": { "lint": "eslint src" }, "type": "module", "author": "", "license": "ISC", "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", "eslint": "^8.38.0", "typescript": "^5.0.4" }, "types": "./@types/index.d.ts" } rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/src/000077500000000000000000000000001513515052500236035ustar00rootroot00000000000000rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/src/index.ts000066400000000000000000000003331513515052500252610ustar00rootroot00000000000000const a: string = 2; async function foo(n: number) {} async function bar() { for (const n of [1, 2, 3]) { await foo(n); // this should throw an eslint error: Unexpected `await` inside a loop. But it does not. } } rassumfrassum-0.3.3/test/ts-preset-streaming/fixture/tsconfig.json000066400000000000000000000010101513515052500255130ustar00rootroot00000000000000{ "compilerOptions": { "module": "commonjs", "moduleResolution": "node", "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, "target": "ES6", "declaration": true, "esModuleInterop": true, "lib": ["ES6", "ESNext"], "outDir": "lib", "checkJs": true, "allowJs": true, "declarationDir": "@types", "resolveJsonModule": true, "sourceMap": true, "skipLibCheck": true, "paths": { "src/*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules/**"] } rassumfrassum-0.3.3/test/ts-preset-streaming/run.sh000077500000000000000000000011341513515052500224700ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required LSP servers are available if ! command -v eslint-language-server >/dev/null 2>&1; then echo "eslint-language-server not found, skipping test" >&2 exit 77 fi if ! command -v typescript-language-server >/dev/null 2>&1; then echo "typescript-language-server not found, skipping test" >&2 exit 77 fi # Install npm dependencies in fixture (cd fixture && npm install --silent >/dev/null 2>&1) || { echo "npm install failed in fixture, skipping test" >&2 exit 77 } ../yoyo.sh ./client.py --rass-- tslint --stream-diagnostics rassumfrassum-0.3.3/test/ts-preset/000077500000000000000000000000001513515052500173375ustar00rootroot00000000000000rassumfrassum-0.3.3/test/ts-preset/client.py000077500000000000000000000101751513515052500211760ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test client for TypeScript preset with ESLint. """ import asyncio import os from rassumfrassum.test2 import LspTestEndpoint, log from rassumfrassum.json import write_message async def main(): """Test ESLint diagnostics via TypeScript preset.""" client = await LspTestEndpoint.create() # Set working directory to the test project fixture test_dir = os.path.dirname(os.path.abspath(__file__)) test_project = os.path.join(test_dir, "fixture") os.chdir(test_project) # Initialize with workspace configuration capability capabilities = { 'workspace': { 'configuration': True, }, 'textDocument': { 'publishDiagnostics': { 'relatedInformation': True, 'tagSupport': {'valueSet': [1, 2]}, 'versionSupport': False, }, }, } init_response = await client.initialize( capabilities=capabilities, rootUri=f"file://{test_project}" ) # Verify we got a response with capabilities result = init_response.get('result', {}) server_caps = result.get('capabilities', {}) assert server_caps, "Expected capabilities in initialize response" log(client.name, f"Server capabilities: {list(server_caps.keys())}") # Open the TypeScript file file_path = os.path.join(test_project, "src/index.ts") with open(file_path, 'r') as f: file_content = f.read() file_uri = f"file://{file_path}" log(client.name, f"Opening {file_uri}") await client.notify( 'textDocument/didOpen', { 'textDocument': { 'uri': file_uri, 'languageId': 'typescript', 'version': 1, 'text': file_content, } }, ) # Request diagnostics (pull model) log(client.name, "Requesting diagnostics...") req_id = await client.request( 'textDocument/diagnostic', {'textDocument': {'uri': file_uri}} ) # First configuration request (likely from eslint-language-server) sreq_id1, rparams1 = await client.read_request('workspace/configuration') items1 = rparams1.get('items', []) log(client.name, f"server req #{sreq_id1} for {len(items1)} item(s)") await write_message( client.writer, {'jsonrpc': '2.0', 'id': sreq_id1, 'result': [None] * len(items1)}, ) log(client.name, f"Sent null configuration response #{sreq_id1}") # Second configuration request (likely from typescript-language-server) sreq_id2, rparams2 = await client.read_request('workspace/configuration') items2 = rparams2.get('items', []) log(client.name, f"server req #{sreq_id2} for {len(items2)} item(s)") await write_message( client.writer, {'jsonrpc': '2.0', 'id': sreq_id2, 'result': [None] * len(items2)}, ) log(client.name, f"Sent null configuration response #{sreq_id2}") # Read pushed diagnostics (push notification) push_diags = (await client.read_notification( 'textDocument/publishDiagnostics' )).get('diagnostics', []) log(client.name, f"Got {len(push_diags)} pushed diagnostics") # JT@2026-01-07: Use to check here that at least 1 diagnostic was # sent, but now we don't because typescript-language-server seems # to have an internal race condition where sometimes it pushed 2 # diagnostics, and sometimes 0. It's enough for the purpose of # this test to simply wait for it to send a (potentially empty) # publishDiagnostics. # Pull some more diagnostics (pull response) diag_response = await client.read_response(req_id) result = diag_response.get('result', {}) pull_diagnostics = result.get('items', []) # Test ESLint diagnostics (and here we do assert that something # actually came in) eslint_diags = [d for d in pull_diagnostics if d.get('source') == 'eslint'] log(client.name, f"Got {len(eslint_diags)} ESLint diagnostic(s)") assert len(eslint_diags) > 0, "Expected at least one ESLint diagnostic" log(client.name, "OK! Got diagnostics from both ESLint and TypeScript!") # Shutdown await client.byebye() if __name__ == '__main__': asyncio.run(main()) rassumfrassum-0.3.3/test/ts-preset/fixture/000077500000000000000000000000001513515052500210255ustar00rootroot00000000000000rassumfrassum-0.3.3/test/ts-preset/fixture/.eslintrc.cjs000066400000000000000000000011021513515052500234210ustar00rootroot00000000000000module.exports = { env: { node: true }, parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", ], ignorePatterns: [".eslintrc.js", "src/**/*.test.ts", "@types/*"], parserOptions: { sourceType: "module", ecmaFeatures: { modules: true, }, project: ["./tsconfig.json"], }, rules: { "@typescript-eslint/require-await": 0, "no-await-in-loop": 2, }, settings: { jsdoc: { mode: "typescript" } }, }; rassumfrassum-0.3.3/test/ts-preset/fixture/.gitignore000066400000000000000000000000401513515052500230070ustar00rootroot00000000000000node_modules/ package-lock.json rassumfrassum-0.3.3/test/ts-preset/fixture/package.json000066400000000000000000000006271513515052500233200ustar00rootroot00000000000000{ "name": "module-starter", "version": "0.0.1", "description": "module template", "main": "lib/src/index.js", "scripts": { "lint": "eslint src" }, "type": "module", "author": "", "license": "ISC", "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", "eslint": "^8.38.0", "typescript": "^5.0.4" }, "types": "./@types/index.d.ts" } rassumfrassum-0.3.3/test/ts-preset/fixture/src/000077500000000000000000000000001513515052500216145ustar00rootroot00000000000000rassumfrassum-0.3.3/test/ts-preset/fixture/src/index.ts000066400000000000000000000003331513515052500232720ustar00rootroot00000000000000const a: string = 2; async function foo(n: number) {} async function bar() { for (const n of [1, 2, 3]) { await foo(n); // this should throw an eslint error: Unexpected `await` inside a loop. But it does not. } } rassumfrassum-0.3.3/test/ts-preset/fixture/tsconfig.json000066400000000000000000000010101513515052500235240ustar00rootroot00000000000000{ "compilerOptions": { "module": "commonjs", "moduleResolution": "node", "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, "target": "ES6", "declaration": true, "esModuleInterop": true, "lib": ["ES6", "ESNext"], "outDir": "lib", "checkJs": true, "allowJs": true, "declarationDir": "@types", "resolveJsonModule": true, "sourceMap": true, "skipLibCheck": true, "paths": { "src/*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules/**"] } rassumfrassum-0.3.3/test/ts-preset/run.sh000077500000000000000000000011071513515052500205010ustar00rootroot00000000000000#!/bin/bash set -e cd $(dirname "$0") # Check if required LSP servers are available if ! command -v eslint-language-server >/dev/null 2>&1; then echo "eslint-language-server not found, skipping test" >&2 exit 77 fi if ! command -v typescript-language-server >/dev/null 2>&1; then echo "typescript-language-server not found, skipping test" >&2 exit 77 fi # Install npm dependencies in fixture (cd fixture && npm install --silent >/dev/null 2>&1) || { echo "npm install failed in fixture, skipping test" >&2 exit 77 } ../yoyo.sh ./client.py --rass-- tslint rassumfrassum-0.3.3/test/yoyo.py000077500000000000000000000061341513515052500167710ustar00rootroot00000000000000#!/usr/bin/env python """ Cross-platform relay for rassumfrassum tests. Connects a client script to rassumfrassum, relaying stdio between them. Usage: yoyo.py [client_args...] --rass-- [rass_args...] Example: yoyo.py ./client.py --rass-- -- python ./server.py --name s1 -- python ./server.py --name s2 yoyo.py ./client.py --rass-- python """ import asyncio import sys import os from pathlib import Path async def relay_stream(reader, writer, name): """Relay data from reader to writer.""" try: while True: data = await reader.read(4096) if not data: break writer.write(data) await writer.drain() except Exception as e: print( f"[yoyo.py][{name}] Relay error: {e}", file=sys.stderr, flush=True ) finally: try: writer.close() await writer.wait_closed() except Exception: pass async def run_relay(client_args, rass_args, env): """Run the relay with proper cleanup.""" # Start client client = await asyncio.create_subprocess_exec( sys.executable, str(script), *client_args, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=None, # Let client stderr pass through env=env, ) # Start rassumfrassum with provided arguments rass = await asyncio.create_subprocess_exec( sys.executable, "-m", "rassumfrassum", *rass_args, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=None, # Let rassumfrassum stderr pass through env=env, ) # Create relay tasks for bidirectional communication await asyncio.gather( relay_stream(client.stdout, rass.stdin, "client→rass"), relay_stream(rass.stdout, client.stdin, "rass→client"), ) await client.wait() await rass.wait() crc = client.returncode rrc = rass.returncode print(f"[yoyo.py] client rc=${crc} rass rc={rrc}", file=sys.stderr) return 0 if (crc == 0 and rrc == 0) else 1 if __name__ == "__main__": if len(sys.argv) < 4: print( "Usage: yoyo.py [client_args...] --rass-- [rass_args...]", file=sys.stderr, ) sys.exit(1) script = Path(sys.argv[1]).resolve() # Find --rass-- separator try: rass_idx = sys.argv.index('--rass--') except ValueError: print("Error: Missing --rass-- separator", file=sys.stderr) sys.exit(1) client_args = sys.argv[ 2:rass_idx ] # Args between client_script and --rass-- rass_args = sys.argv[rass_idx + 1 :] # Args after --rass-- # Set up PYTHONPATH to include src directory repo_root = Path(__file__).parent.parent src_dir = repo_root / "src" env = os.environ.copy() if 'PYTHONPATH' in env: env['PYTHONPATH'] = f"{src_dir}{os.pathsep}{env['PYTHONPATH']}" else: env['PYTHONPATH'] = str(src_dir) exit_code = asyncio.run(run_relay(client_args, rass_args, env)) sys.exit(exit_code) rassumfrassum-0.3.3/test/yoyo.sh000077500000000000000000000033471513515052500167560ustar00rootroot00000000000000#!/bin/bash # Cross-platform bidirectional pipe for rassumfrassum tests. # On Unix: uses mkfifo trick for clean stdio piping # On Windows: falls back to yoyo.py (Python-based workaround) # # Usage: yoyo.sh [client_args...] --rass-- [rass_args...] # # Example: # yoyo.sh ./client.py --rass-- -- python ./server.py --name s1 -- python ./server.py --name s2 # yoyo.sh ./client.py --rass-- python # yoyo.sh ./client.py --some-arg --rass-- --logic-class custom.Logic -- python ./server.py set -e set -o pipefail # Detect Windows (Git Bash, MSYS2, Cygwin, WSL) if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || -n "$WINDIR" ]]; then # Windows: use yoyo.py export WINDOWS_KLUDGE=1 SCRIPT_DIR=$(dirname "$0") exec python "$SCRIPT_DIR/yoyo.py" "$@" else # Unix: use mkfifo trick if [ $# -lt 3 ]; then echo "Usage: $0 [client_args...] --rass-- [rass_args...]" >&2 exit 1 fi CLIENT_SCRIPT="$1" shift # Collect client args until we hit --rass-- CLIENT_ARGS=() while [ $# -gt 0 ] && [ "$1" != "--rass--" ]; do CLIENT_ARGS+=("$1") shift done # Skip the --rass-- separator if [ "$1" != "--rass--" ]; then echo "Error: Missing --rass-- separator" >&2 exit 1 fi shift # Remaining args are for rassumfrassum RASS_ARGS=("$@") # Set up PYTHONPATH REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd) export PYTHONPATH="$REPO_ROOT/src:${PYTHONPATH}" # Create FIFO FIFO=$(mktemp -u) mkfifo "$FIFO" trap "rm -f '$FIFO'" EXIT INT TERM # Run client < fifo | rassumfrassum > fifo "$CLIENT_SCRIPT" "${CLIENT_ARGS[@]}" < "$FIFO" | python -m rassumfrassum "${RASS_ARGS[@]}" > "$FIFO" fi