From 9bbc32b7528f9acdecc7a7ecfd2a8037edd88a08 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 21 Jul 2024 13:23:15 -0400 Subject: [PATCH] Initial commit --- .copier-answers.yml | 10 ++ .gitattributes | 9 + .github/CODE_OF_CONDUCT.md | 76 ++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 38 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++ .github/dependabot.yml | 16 ++ .github/workflows/build.yml | 87 ++++++++++ .github/workflows/docs.yml | 30 ++++ .gitignore | 144 ++++++++++++++++ LICENSE | 201 ++++++++++++++++++++++ Makefile | 125 ++++++++++++++ README.md | 27 +++ docs/CNAME | 1 + docs/logo.png | Bin 0 -> 65980 bytes docs/src/configuration.md | 118 +++++++++++++ docs/src/home.md | 53 ++++++ docs/src/installation.md | 27 +++ docs/src/overview.md | 55 ++++++ pyproject.toml | 148 ++++++++++++++++ setup.py | 1 + yardang/__init__.py | 1 + yardang/build.py | 136 +++++++++++++++ yardang/cli.py | 52 ++++++ yardang/conf.py.j2 | 155 +++++++++++++++++ yardang/tests/test_all.py | 12 ++ yardang/utils.py | 24 +++ 26 files changed, 1566 insertions(+) create mode 100644 .copier-answers.yml create mode 100644 .gitattributes create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 docs/CNAME create mode 100644 docs/logo.png create mode 100644 docs/src/configuration.md create mode 100644 docs/src/home.md create mode 100644 docs/src/installation.md create mode 100644 docs/src/overview.md create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 yardang/__init__.py create mode 100644 yardang/build.py create mode 100644 yardang/cli.py create mode 100644 yardang/conf.py.j2 create mode 100644 yardang/tests/test_all.py create mode 100644 yardang/utils.py diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..7fdc701 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,10 @@ +# Changes here will be overwritten by Copier +_commit: a84eb20 +_src_path: ../../templates/base +add_extension: python +email: t.paine154@gmail.com +github: python-project-templates +project_description: Easily generate sphinx documentation +project_name: yardang +python_version_primary: '3.11' +team: the yardang authors diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7c39058 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +examples/* linguist-documentation +docs/* linguist-documentation +*.ipynb linguist-documentation +Makefile linguist-documentation + +*.md text=auto eol=lf +*.py text=auto eol=lf +*.toml text=auto eol=lf +*.yml text=auto eol=lf diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1b32efd --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at t.paine154@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..42cac77 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "part: github_actions" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + labels: + - "lang: python" + - "part: dependencies" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0a1c612 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,87 @@ + +name: Build Status + +on: + push: + branches: + - main + tags: + - v* + paths-ignore: + - LICENSE + - README.md + pull_request: + branches: + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.11"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'pyproject.toml' + + - name: Install dependencies + run: make develop + + - name: Lint + run: make lint + if: ${{ matrix.os == 'ubuntu-latest' }} + + - name: Checks + run: make checks + if: ${{ matrix.os == 'ubuntu-latest' }} + + - name: Build + run: make build + + - name: Test + run: make coverage + if: ${{ matrix.os == 'ubuntu-latest' }} + + - name: Upload test results (Python) + uses: actions/upload-artifact@v4 + with: + name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }} + path: junit.xml + if: ${{ always() }} + + - name: Publish Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: | + **/junit.xml + if: ${{ matrix.os == 'ubuntu-latest' }} + + - name: Upload coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: python-project-templates/yardang + + - name: Make dist + run: make dist + if: ${{ matrix.os == 'ubuntu-latest' }} + diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..6ca647c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,30 @@ +name: Docs + +on: + push: + branches: + - main + tags: + - v* + paths-ignore: + - LICENSE + - README.md + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + - run: pip install . + - run: make docs + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/html + force_orphan: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1be413e --- /dev/null +++ b/.gitignore @@ -0,0 +1,144 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so +*.dll + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +junit.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# PyBuilder +target/ + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Documentation +docs/_build/ +/site +docs/api +docs/index.md +docs/html +index.md +_template/labextension + +# Jupyter +.ipynb_checkpoints +.autoversion + +# Mac +.DS_Store + +# Rust +target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3e8cd66 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 the yardang authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afbf10a --- /dev/null +++ b/Makefile @@ -0,0 +1,125 @@ +######### +# BUILD # +######### +.PHONY: develop build install + +develop: ## install dependencies and build library + python -m pip install -e .[develop] + +build: ## build the python library + python setup.py build build_ext --inplace + +install: ## install library + python -m pip install . + +######### +# LINTS # +######### +.PHONY: lint lints fix format + +lint: ## run python linter with ruff + python -m ruff check yardang + python -m ruff format --check yardang + +# Alias +lints: lint + +fix: ## fix python formatting with ruff + python -m ruff check --fix yardang + python -m ruff format yardang + +# alias +format: fix + +################ +# Other Checks # +################ +.PHONY: check-manifest checks check annotate + +check-manifest: ## check python sdist manifest with check-manifest + check-manifest -v + +checks: check-manifest + +# Alias +check: checks + +annotate: ## run python type annotation checks with mypy + python -m mypy ./yardang + +######### +# TESTS # +######### +.PHONY: test coverage tests + +test: ## run python tests + python -m pytest -v yardang/tests --junitxml=junit.xml + +coverage: ## run tests and collect test coverage + python -m pytest -v yardang/tests --junitxml=junit.xml --cov=yardang --cov-branch --cov-fail-under=1 --cov-report term-missing --cov-report xml + +# Alias +tests: test + +######## +# DOCS # +######## +.PHONY: docs + +docs: ## build the documentation + yardang build + +########### +# VERSION # +########### +.PHONY: show-version patch minor major + +show-version: ## show current library version + @bump-my-version show current_version + +patch: ## bump a patch version + @bump-my-version bump patch + +minor: ## bump a minor version + @bump-my-version bump minor + +major: ## bump a major version + @bump-my-version bump major + +######## +# DIST # +######## +.PHONY: dist dist-build dist-sdist dist-local-wheel publish + +dist-build: # build python dists + python -m build -w -s + +dist-check: ## run python dist checker with twine + python -m twine check dist/* + +dist: clean build dist-build dist-check ## build all dists + +publish: dist # publish python assets + +######### +# CLEAN # +######### +.PHONY: deep-clean clean + +deep-clean: ## clean everything from the repository + git clean -fdx + +clean: ## clean the repository + rm -rf .coverage coverage cover htmlcov logs build dist *.egg-info + +############################################################################################ + +.PHONY: help + +# Thanks to Francoise at marmelab.com for this +.DEFAULT_GOAL := help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +print-%: + @echo '$*=$($*)' diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f539e1 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +yardang + +# yardang + +[![Build Status](https://github.com/python-project-templates/yardang/actions/workflows/build.yml/badge.svg?branch=main&event=push)](https://github.com/python-project-templates/yardang/actions/workflows/build.yml) +[![codecov](https://codecov.io/gh/python-project-templates/yardang/branch/main/graph/badge.svg)](https://codecov.io/gh/python-project-templates/yardang) +[![License](https://img.shields.io/github/license/python-project-templates/yardang)](https://github.com/python-project-templates/yardang) +[![PyPI](https://img.shields.io/pypi/v/yardang.svg)](https://pypi.python.org/pypi/yardang) + +`yardang` is a Python library for generating [Sphinx documentation](https://www.sphinx-doc.org/en/master/) easily, with minimal local configuration overhead. + +[`yardang`](https://www.britannica.com/science/yardang) makes building [Sphinx](https://www.sphinx-doc.org/en/master/) easy. + +*This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base).* + +## Installation +You can install from PyPI via `pip`: + +```bash +pip install yardang +``` + +Or from `conda-forge` via `conda`: + +```bash +conda install yardang -c conda-forge +``` diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..50f155b --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +yardang.python-templates.dev \ No newline at end of file diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c850adefbb4aff67ffebb83a63ca1c622071eb21 GIT binary patch literal 65980 zcmeFZi940s`#=7eqlk!#A}UHk6f$j@8Vym(*i2N&m~p4Uj?kddv?H0qDI~MXP|8#y zv$kX&Hn!RJz1OqpoX_?96Mpaab)D;d4(oZ=TKDj}U-$61ZDgR&&9#mT!!Yjs`*eNxx#r_;U@E*Q2$0{z3}8L#P%VPe>R9W7JO$bnYx@CKT1H+51@ z=q?{S$6EFrY5n!<`R`r1FaKrZ<%0?;E~d{8XuCW={H)|=WZB`+Hycc{GcQ~b?CbSX zyYwwhrF>|AGtD&HR>`9Jn|t2nZGRoqApZ#MhVDr?KZSgL9 z-T%q*e@291XaDCt|AP$-Yy2Nx{x48q|38k9=0{dy*tcH$;CJyE$6~^Kue#*V=3b#6 zJ2Nz{bo%|*xFlP}7e!YuAL!%c?rl~LEiV*}*{2?76cPCBh#?{sWbumWD0?iCSXgE5 zni)0iVzQ;4^0sY%9DU7WLp8qObQK@th55!PSv`lb5#4$7edpef)-7N6U4MSq+Cx_K z_?mRlyzRZW>im0t_tf&gzS>OPtNF)4U*he+B!?KhpLkFrh^5$K+#@FcnRV{TyqzM|2ds9f@>xQd0Ft}WCE|Ftq|t|Dm0Z|;ob%d(Xd)hhFyocwyH&u*Hxczyu?JNeN7tP)(DX zWkq`9xhWP-j3BReF6kN1hTc#!9`|TEHTrQgMbzf$8a_VRf-Bn#Gi*1Bi%)F07&I7o z{}8K~ys-#zJ1tXx+*)N-Hq9WYEhn%uXshIb>BBg$+ff<5Ut9j%x0+s2*(c7wY%;oI zE|W;4_fZcz%N-TVGrYsf(nNpUa3LN7qGxJtV=E(Q^Fv^`@U4oVjmz;lcA{Rb>j|Af zTAeh8C6Z0rHTRei{HKdQD0`kgeX+6Dd8Vl)u9+zrq8s^$SChnLi#5#37;mkQZizE+ zNFt>&@~!H5y^`1=1%LX_+ey^x^Dd{Ix_?hiESEgp5N7}SYU0+p^d`yR^YTofDHtXPS?3$#GR^C`##{#ynK$CQgwHwMGtzSN>%2Xke zLdPXRH%-ytTnUZbx!++n8vn@AtH-`~7Wqy-*~cSS1&@~{8f|fClm!t zws6Nsvtp)C#dt7+?pKhA-%x9wp{DzM7t8X_-b>#pn*Qb9pPBj&!|8`WibJ_cTQ!{e zPlf4lU_ssHAV;oeV12Kw1z&reD0$6q(%D!r)=6DxU1c~N`qNK}wuhKcqF{Bppgs+H z^vgBJXYM;VVdO$qAmZE7vaTu%+7y6deMieRJ@o$s1Gj0Syq}nYY80nK{WCU-`>E4-vE=>B5dSAqH{NBj3~#m!Vv#$a z(8lNIv9L1k%o|=FEc}K8Y`1f=e)(v(P5zDzvoQ}kKMRRZtUp|^g4_af(Th)!p`-7MkZzuFs2u3(-I*V-8Q(I?r_>rJQWZ+`Y0qsMO+2Ddj2tvirPuwvPF&nDYUf6F23ILbo|bq`uaan6&4c~c;^n}I-_v3;B%-G* zOB@38Iu9CF1<7hgc<~ia@s~2Kt4q>!lxrVb zsT>jML7G$6mEPsoZfy)=QGAm2mgGGvu2Ea`UE&)P(T5Ye@tlnfwRxN(h*r^skR$+ELdV#^aM$Bw)EaqvyG@g2@|pG$4&(rGdTSX6X&^7 zZrRe@wmJtasOyM`n8J>aOjz58EKuB?{pG)(|9p2|YJ+mi`k1Ntp`U7X;a;nXH^Z%W zZoI{UMPBy+DHcC*gR4qvC<*u%O*T#a6n$Ghp`9eeWT^%xr{p$?7fuzDetcm^hnGs= zPmi|-OMB?nIVVJM^jD`)i%y8dECiE$^-W}%*QgE)a3m&zcn*J&h+YVZ0s|_Zt_#&s z^mt(n$>_tkUX3R&wvYG@3v+DS4@=2z1$^z^9r4Fi<<_KDend79-u_8ANeIITU0GQo zcS0Q4J5^&;M0}&N04wys3ls0shs=Gt6#~z6K6~HuL@nG4OvddUQrZVgOM%mC3{uu8q$PSQ_?XSpypl_PPwrKmFVfS4;;So|iMpq&p z>HGA*k9vKYT3=^^Vab(9lmcmIMvc^{IkuGAW~&gEF385iP5nJ&tXoF@cz=}ibX3L& z8<9y8tX^6Fu@{k&1=*d=ZK6*b_qGZ1U`BEcU?N6JX=T^mfX5s-Yvoun!QDQY=!y}& z@T0h8fnlMWB0A2*RXrENrX~_rmX__hUR!@f4xKw6H_lw-Aox9JHcKQW zV1O@4sWv-TwR<_)wkR*$LwcfqP-@n~2%!H%tp%jmtLH+h;yv<5J?QS5Nr?_^Ul^n~g7k_VL zYjue)tZf6;J7(r`W|qhybx>b6E{Xk-UXe2|yOJJbAq3EN9DoawOIhE!>u^=fX$RU+ z>}eLub*fO&+^zB?7B^5w-DeO5wQj~r-g_n*-CtQ0iTrL|s*;*e``kcEZ9^|y%ojw( z)^8u|>GOl?Ida`1&h3L!p9=WOEhMdd_JBjwI^Vq0mEyfzQqk4hF}6LlcjMRMn~#9? zpWKeC_zD;*e(qN`JMVjO-CYa17VG!=*!c}&(Ljj9&yN>el%ok?SdbH- z=yJezrB+ap=S=i`?e}OeP9rzafi8-LlN&s@UXEPY+qV_$^fOXdcv=2h?@o)Nw+b_R z96)BPw*;|R-Y^+nIgGpo>UX*>$ok{IHK~ahwq{G$Z@-^K4DQU}C|+~Pd}#g`q(c8L zD4I+fE(3fyNZbaNTh>86T7G7bI2p)-ovp?<`v&IuL0RrRoJYzvOLuPGclON5-nU2x zYh;nG$l<%w*4{yu!<@LvSfqm)a6G!u*8gk7YbA8UvIj`^QmKG3o$z*(YHS<8y>b{* ze1C&G7pO6$0^h2>_}eAeY8z~&GVakpNt$)nbr^F(H-ABaP>dZoyoN(h8y*e@Tru<+ zTB`*=#9i;o2W6*?@SNfC2I1Gvl_&ewf}C#R8>^oZQ5J=B$?TR9Dq+Hum*Wq3!2>(s zfrt$@Zff;BsMZ&WuTNx!^}@y?Vg~F~WEj;ze81v`v3Xf%vEi!HYqumxESTIj{O&t% zICrG#IFp~F7;$I+K{Ld3ilw->w+co)O0Z@Mzb3__xE+p3lvgd3MWB9iMLo|7BynkC z9Xz-ZJ!q+jpJ}Ub!?Mnr9{A}mbRNr9_)k@Y8&>E67|~XU@fnNQNui*9(+O$#;h5y) zKv1E_(AxL=tzEpEC%%7x0VnE8@PiV zQ{gHH)LAg1-Y=x-ZG+Xrt!u3=!Sl3DK`bYFFNAsa6_&3NB8mAB0l~Wu z!jUoDI!dz))`XU8ToucmtKaYjwt4tAP3bgH0Az|z_mL?k<|B?L;jzI`oki-d6p!gK zAdTp~NNz+09Q!MTkLSvO$JUAo(c4#;9sxQV)1IebF1aEh8mz|_7yk**DKq*;uO^dp zEJzG?`u;&5KE2^PExm<4@GfpN9hDz7zTd*2|oNIauzX9H#D%mg? z2H6d=1M>0m$*4JpFXN;?7cOMOt%bVoh2=+kG0U;bBynJoA(Pj^7CMl{vE5ee*VsL4@u8F_xT|LQZIYe_To(1tVtp>Y!dZgm~Y`Sx`JF`Xu%UN zChTrR&>Wo-axbNAE|1@NYM(`DoRKsPxs;4pEQZ#Ev#5Ycj#t*+37PP>R#e+2i4bUuF)8K zGvPfO(I&WhHcz$FaHz4178Gs{h&o5F4lCiH129Xe!Vw>)`;QvoQ5p=RuTR=gCN%JE ziACY`Z2d5^(&po=n3G>b%(gy(w^v;k3ZB1QGc}mfnMBKrbqkO)xSD!Af7eV*p~~)+ z)4poqPc?Iy!^^;coI8EY08@Ss+Ag$KzNQQR;tK|${&UXX#D`X7^T3PUvgTuvs~C;7 zgBg@501)83klYHM3?$39E2;GLxuWH(Fk-FOz`{#wOlUX6ecU~rG{6M8!hA=G3&iUce^UU&HG*h4>-^gqVwx>`ucsGfXK8@CT1_9vAEGvRXzAs6-2uG)Sq}c;G|;V z+(ne3m{b#&o4@vSfDi}K#V(RnG25t#1@*pwf%)7<;RbD1=rx){@A}+Up(~TTEq!N_1~XohS7D2hAhxA+BD4h-BqU zv$+}uA%;6PN5bA^Lr~L0(GWX(mHwig^Fd#%AQKSic`n0OZg2>|bVmj{S;Qd5h*9*sUS=DhSmM8jV7A1+{C7be9ckeo38+uImI{Xu^3*xkb(66Se3 zrnOP{l6vIeoJ>(*Nq-rQaqA0kaM4DRm2elCdGeD#D@RzuJZ<}cb}m{P3QJD|A!kIE zbwTjrmqmGk*=-}1$ViRyO|AWCEf?E>XbRbG<)!@v*p7?%HF{eq zavOAPyh=N1~E!i`iNyHH<~du1oP)-Y+!3@iA_JY5&~T-2h(3pk&IZ$L7`!w5@XUO7Pz~RAaDm>|1?T(^%x4 z9TYgF1s(C=zT?E&08dmBBF4fxu444)D||`EXT%l3-hnuv|bYA)V4NT9AauyoOm?>w=jY;$je>&vc@CW7|J1fgJ(1!B7xm`&+zbvJQO}$ z0~4ar?zazKl_~VHK#ZMfny>Vl@$4tVBF4oSM(NSF`Xr@ehJEYAex+my0<UdqF?vN zH~!auO#anS?rs`0V_EUH$2bBh?DEjLG$aAQ|HAtI9poGou%uUQ{s(2il5W8pze8bB z{@i{21QyIlJR!i-!K`od{v}weu=_0D9upV~M^s5YF;oiyJDeou$G#-IwTGW73aCSl zK|mM~+4em{8mtvlzHr*G?$3Oh7+nz8bXISX!2xxqwm)eRBI#Ouu=Ewu+ae3?ILjLY zw%Aoscklq98CX#CPt}dqsx1&0B2Phi9)z?VPH^I?D@>8ZJ8dH=WxcdMasYodEnd<# zJ{q&_i7HZp8v(C~e2DIHH&RRk&sKxRx@PW7HA$wSoR+b+ayM^!4q8E@(zLUP_{?NLaY4# zn8yf0y@tSBbQZr_mwDvQ6B-ViXsn5fwB^blIWDV5qV2d}?jG~g$`|uknDa)E@(JoZ z6~Cb;1^dOrn=U}LB{(+*@+}C)N5Jq-_l?#mys7nXR_)A9dW-_-U2Zs{Zo6ALx(mQl z!qqv5f}KeK%OzdI2h=507bZT=BieDc^Gmg!h#JK_a{N#A)TusBqYJUhPEF6c4x``J zh_O;ORR z(9D8F=JnLJz)McAfs2ZZLmB1eA*jhLk>eJyhM1>g~#@NBMg$TQDQ;3r=_GBaqk^k+8LxAUATt5&&cJ>rpLHgNb zEP_8Bqy*f@MM+N)4D{Q`QZP+wtycx${&&@8(&L~-UzE&(1{$3409<;Qp98{Nfag}+ z{i?Jrj>wYl$>jFMzzN~^&)>UcU5$YV#r8CJC+xyW*^FDS@@%Ny+>Z{(I>}noej1h{ z#vhuza%14uWo3pF>TWq2xNzVu@v4`BSf?}^HZd)*b%TKi<>EX)R_d! zDr_C>P8OJjLQa7Z*nXV9L<0VnWb1TqNuXO%PYVQ!Zu;|5!2@JpA7H7 zfF+mO+S{FMQ)Px6V;N-{cjCyuNfY;Zdbl~*^VV2p#ea0o=7ADHbwvxu)vkpMj|HtO zf)i2QMd}~F9yh7)5$!`}luKcgGjMY*k{oi`+(ZYw-ax!yPu zdW}nU{;>BOL;7VB4K%Sz=sku?f=6NbqEv>5F9z7P_4>o#n> zAr-V0wq{N6OwSC-+-73c9-tf;!2u|p$&NXy*95E73S*IN73eOWtF`jKA)Cmo`WpN%TV(X^0e&%%91$Wb}LH|FPvZBK*`j%8I}gQ1949zUrq3r z_J(M{*|%I{6UpTWzE}UXOKGJSVibI%SFZ!5vw@=28-b|w{4?{ZiS*p#d^K5&A+TG6 zeEMPB=c>ss7NrWU%WY)|Wl$7M(0H@^^vL~zwE`sEo*Gs8<>hlWd9$PFVkEDziybqP z2~e&?Jhx^K?DsfZ_R$QPuWc(Zoz0s`E|6n`P5^+L&YiWk0ge5A_cpS}b3RS%{bc&JI#D&VP>_;0!NmOlZiE zBs4H~Icuj|r?0t%^Z-vw`VS33(5(UV0+2LbkxH5g5Ei#jfKz-}5uJ7|K|CUUi;W!8KGd=IgZ1Ku;i$E9c7d;nmOx-J}({qZ9EVx7~iu%HGUu4ejj_ z>P)%ONaQt@tj@W2af4VJ6Qu0qd1_(>@d$phTvOlof$&?%g8_a{WMAFn%}hwT4K9aS zB_d<$iE32n;9k;rWYC!2O0f1c{L-#Zk!ij^_{O#vr=7?q6diCKk^33%(BVzk$I#@T zq7Y@Ti&9{UTN%IF%bP&Le^u7@lyv*^=0*kV05ESLMfIvmeZiVCB$Iu(XV`vp2hk6T z1!>>R?~lEPw4Hx!xFs>{@|_-41#eD8Jn%UHIbIvwtr@j+>$|$V37h_KuPh774mpot z?WW=Uxd~?CC?AsS&SjMFuc^a%+rSYCTMvl_{mgYafy9FUc2DYXYJhUQ%ln*Q%9{+q zv-smbEPe|UkByo@O_uxsf#JkEF@vqGesROdAK2iGsrXp5YX!sb>So?NCRpIWgq$U{ zQ*s;VTXGr26i;>QpJ)hBzYfKNgOH?!>cLpdaFsM2=1thkklC}tE)XfgalffC(45E! zP=1JOdDoW7I#`kkc)5D#o?(DS=pnBKjLUu{Sds*67a$WBcGQ8_Zwwatde#sMgQ3q? z8{0T(PjKz~`wmL+ubUuuF6d)}SMAcmX+~yEZY9KsrJrur5~1J2&0|drg8c+&3&P@i zGi!&1N`3|YyO(-4~(8 zR0E*|^sqlbS@BO_mr&Wa=kEs~1G|~+QI@4lP{5yS?28hnWnbnkIVE^GZ>R!?iGNOc zgAGt{EuW7ANYhu+`#Npgc#v%L*BBXyBPD?EU|fFPv;8Uj{x0M9eKMmw#TTcNzzS=Y zX^9v-#YOW(1Nhv@-GJ_Q7tx&{&q=9agptn9usYDA0OV||ugpYDAZZzO34 zGsHC5o?NPs_ZTb{VJ;-7AuI0a`nvk+q!T$(ak97z5B5xCUi@+mE^4F(WMRFG=Rd21 zv#f)f4|oTRZCpaLK;&H-6C)9@wJVH$z$*jcNPK3pMRI>uj^mL&UvqjkdJz!PAeq20 z5v5G=zBp0-lIPl&3+E^dnc=~g$XKA`BTJHfw=ve;d}3ANouks}h?~_gcbY)J>tMx? z)~O}ZlX#7;fy_|m%J6TCH^&H_#OK?asD@KIieDMiVRUN7hSE zzrXDn2+;FmGf8_7Zp5!Wwwy*5v4WTe4KpZMo7#+Q)Dp>Mc+k&()IllvJm_+XkxOwB zD@1)kf&&DgsTMP|0RP^=^nmZ#OC=qEGsFh`%kkJMSnDfoiTuwZ^ENE|s{pi5_(I_0;G8NSz8!Ygg2ufiMd^vkaM z0(qK|C%nPkZ+;HU(h7RO7*>(OFt_ojr!cg@z~keSMvfD##57!QU7JYS8*!)&`K-Pe zWiTsfN5ZrEyn%aBy|iYX!|uw@(OAuDBxEN$xo}h!*1SOBka&6>KpdOW*>tRFU@B|st&sKC=x>4zHj08Nj=5U^ z>(3i95&8G*bWwDt(-wpy=;K8tC850oDvGB*PLTT3MS=?3%-$qmu94+XXTVA&NZRo} zBfgq3>d$3%L~Dw8_)Qf++xezkO}o(WyBd48qMxz@1c+w;+zosu?9qG7(%j_~{9SCwE3s+x0co;?fsJ8*|PB?ye?CeeQM z`$_-+9xah={crozzDCywiq2gR1OXGi)4Qta8J@pA+GOttefmp&dX$lcOR>Wfg8+7q zM$P9M&3MX$Hu!GyX2%oeJnY@9vYhf@;mbm}`gQihqCpHYgRRFymsRQ#fg}h?jb%-#$M`A24w3gCF3dzZ zH=bAyFizeT@dpsBM+%*($2>rJ#Qa==VEa_OWap=^wP5W|2av7@I2G-#7(!@9SsGyF zLssVw+V-OHxhI-6G3s$$)_ZpHd-zbs0QEbovDEz7@CFJWpGV7}xd`#Js%@=LyNtD1|L!n!i)HvNcnVzNG;-x*QFcXmEDbjY zv-wFIXPj>8GL?eIKEh`FP#RhyPvUpl1pA+D0iY>$hxm26x8~%;igm6y*kPGbh<5#6 zRdQ`h__VoC^K#j;Dy#NSApV5kZ~J|+J&jvtP1PArQgaNQ_^v)Lhr#;_2T1*Db}!5p zEyMxxIvq%}_IvI?4fy$&##?f^BV7*4TKCC*dm4 zaQabw){v|v{1)8Y4(7i=SNZ?(b*{^_!wqK*?IOZ7l-DZg7e#v+&pfNLI5O7wL%r<(j=?w>S>^%J@|$h~l;++%p^;AzP&E&CYFJpu3Z~;)@~N zh3vxWOy;iektw7eG%UOKG!9fuz95}mpy-=7fdLS^2O!5H*RxxOrb@aq zY{3I=brWf*W~<$8}iK@Re}zub_lwTP)#@SAGD zO|`PykGDx@-d_Lxb$>78V3&Q{k2dn#)j+S$}N8ff{xPe{BW5 z+qLoH90hVk;v-ZKBi8emT-$W6P_TXCLooH{;*myth0q=fh=Sr&>g zJTJD+u5RL3==cS8P?IU`fBD4r_3OSyYhACuv;o2YPW-4l5sT6u2GBRPe41Vp?b~(I z=T~h4;1A(DgYqca`D~-~ync?3iw7AQW6=}B@}4N9ezSSrIvQv}VYqEgc*O z5g&)UMi%=24cZ1Ulk6I?)8=^rsLOAGo{Fm#_e<7wnWyp`sIkXK3=8i8|9*zdKXMh& z06kzE|I{^5gp0<{`gE&8%$ItJa|UZ(Z(DJV$<+W?RNSUrkjvJpgY2^^{AB3O!gMez zOz|AKNY<|_LG}||eaHu@0>w#)XA&Fxf!o!F@UaAEJjU{$zp)869C#A?cv2K(fgL_U zYwJSi|BIl?n~}WaR2Rt{X3)oCHh*a1#@iVsVGT%>uxC3RJ>}Ah?6xy(*?>8y(h#Z( zWaj{7Be~%d)bH{1s&ATpGsCAFzE=ZxBEEc8*o%YQj8REddQ1!$9h6rA=+*${lCRMV z-%XDOL$LdyUk34Iaf4&|mjF+>%^6YuCAxU=XKvtmwUTVkPzp7I-JN82Io zAiivwDNK1ISwsGp5gf+tgkP9ZX!&0C$(p$)91?f`k(C>&pzd}!=dZ2Tki*pHUV;fR zo>6jVa7F-h5SZp{#rvrbafXmx6JIjI;5GL7&+U^X*FG)>U;K)zF(1%Q>K?ql{a;g& zb3oB?5%V$`?C=Dx7SYWlmd%;=U8@`UoBHA<6sWV4jyP204CVp{sUhQ}LICQLKvSC{ zvzJo{Te-(4OBoQB5@O@^4IuTVD{XYSRj|;ff(MRZ>~hjbup=zm(y{dVFfIz=LMkr% zOSEtIp1$kkkEupFpDsSC3AHfx)_KjHNnn)7*8TIBZ9+i{5)UyR(*up^r4r&p@0tzc=K1J6aL!P*;fl~VVZ#@H(L=2m7a~TCo zcb^UETDUpu*g|UrhXxe23MUcp|AHYid4%t_B$MwJCl6+c^XmQ>G=gXSCr0em9WN&VkSr*Rc;7R{0lQD#G&8j z`N}U>>Z)aUUw{xoLcE;(1jX4}n64*8;A<2Sf7Wn>7_%-DFNa}#DE1Jjj9S+lGb|jn zg?bDSZAYY_#RPI2S2ko*K{~t~J!?P)HBl%)B|uD8&tj6^?=G6pZKb;tyGi!S=SH%!s(dOtW(y7NK;dCJHR|_-_xL zrC9(x#Dg7V7eSHa6MW!>5rk60UWKgjGlT2G91{=L+8f{^REqnN*7XfP|17P0oQ&4x zL$wt{#oyc?gGw`(sj-lxG6FC`7oD%>CYayQk%tacRSoJigk?GDw9?=+jBE!k!B}(- zEi%3Nf1&Ew<*CwxNX^{M8A8}m#6AewJyFsm7T4DR(E2(p&e=%WT@^7F$8KxJeL*=? zebg7A1FLc0R_einCfxmT$_K=8FCbepSWUmPBlw5Ki>>6bx!(|5FD^w%<=e@Y!8S#I zpcDhgJUXqAy8@|RgZ2QD=pYn^#2$$87%3lpr_=mjs51Rk-Sg(%Z0!IW;Rj;OP>SP_ zdXa1yJ6~oMQwbp|;|c)$BIwFN;>uuciM+@FpfZQuJa+S7Cd#H3`K+Au63@WRiEtCW zwdMlL8DmXvz#yrA;GF*B?>|3LnQ29Q@SjDF@xa@j`r!+NVQe*SfRZTILQTbSTAPc| zh_D#tQR*=h@i8!tL-3DrpfWd3l>+G^=;B*!1yQ8RdUIkjMxZeWPdlyK{GnHYDSplJ zbiQM#%V=E~F(H?84FF*r0NnQP{dB0sc@pQ19~ngjna_W@n<-T7Fl-RqF8tWhv{Fg; zL*;1+cnssV@cq8)zd%I1kD@9$3asH9Xbk2=79qLjpQ|6QJJ0K$X;iSQDS7e5k6eI! z!|w4CPd2)8G)K^XG=83XQ$GfBag5sqoCODc?~CZ=+YE>! zXj=vgl|xas+85e|H88&jng27mnhJU)x*GL1PJwRuo;Ra=$j7)uY_q^Dug0FzJ~N1S%?OT0~5l& zkR!cn6SrU*{FMM-8)OpZ?@TU-uMYn(vZ*sR^a!NqyUVMr0FcDMDhK9frok##A&Bn~ z_@Q5~x1vZI6lFy|0C8q9zy64BmK*2=x%Ot{5ye{*|N5-e3pfNSKKMCOG!etQ^-fSW z^e^oqe8vr8la>gv!6g3w+QRQeE^nT3!C-=wd=^&)7(?-G(=I#`IG49F6r3~K{pjeQ z>khg~f6F!DBxDX5n@B89JCEif60trn+D`-7*o$qVmeSYVBJ`nzw;AUUaZ8CUw(dy0 zP3~d_Y81aRZFTFEDS)pz40^&)`gm2yOivl9PyyJn5%1BN_*p2H+>uqAF@;W`-M)vQ zHP^J#YsZiecbsK)#-5p?*Zu24PDlRM3CTt^3Aa%_;=ug3`Sy!b7vVk1TY(YDFU3b9GjhHT zWzhKUq$b(cg{j`6U@Jpl^H6)R@5*|NLV8N}K6Ap@94o*kRD+3cQ<@~qHFwgd0WqD9 zd~|6S!7E4C*-?jKnl=VV#D;Jis3n2JbP%^@m@XvqGAfJfGKZeG0r+U4NMYLCaRpu( ziUu@VZQDl3PziXl)F5UOfPsAY(2N_pN1xKcHCzv^PA@HD@UM;+$j%wv12$ymMu&kr z7k1nlSPiuCM_Q@oOvl;3_x)>$0(a0WY0D_srsSrqsf=`yJD)XY({X+IO$)U>1$Ld< ztae|WNoo!FI?*0nw}<&?o?!cke%N_nv=4Is%{+)W)Bg%%l5k3RrR(ivt6uSARZmVZ0kSMaPRB z${xm+YW}E<@$;WnfIR$m-&Vox+=2Nr+J>%Qp(M0eZ*V$NG;#3pIYTlN_>dmbRrq~S zsPU`9B=a!4`T1%!)SWlz``egM>@e|vT7XHxfIXZa#3pqgq_ObJ%)OLRAL)3pn696M zbdnLWtq@rut09LUe*hkF6QK_Bh`6u?H}GQISRrv}VT*hcvkH!&&FG1FJ{VrbLiS8{ zm!W${WTQj_0okoyZN4zW`Lh!%$ie8q0_{|>Hq@=f?93mgmB#p7@b~GQevB%Y`(>7@ zbl2Hi=Iy|BB2d9tGi#gxHe#>N zs5kcDC6`j?#T1%gakSTbu9{APjxz)z|9Q%eC$i!o9rq>7hM=L z0}Ll<^Fwq$df0Q_u*En@RwhJ26g+4F{t)Hp4arv*RFPQ-g?dIP$78NxA0CHCp}zPej31^ z=tAG0yf?OT2Zi6lI6n-28GP9QMSjiG@Cu=pyYOXA?uSbaR%;ttGgQz!z~z;@&FV{m ziI>F-(Zd>DFtPKYeo7mzj`seXAJnD}NNc=1ssrVq4ef3QmelG6hY10(rwBJZz*9Po zLJ{=Tc3-DAJB#l4_vAR1X;=FyKy)-(m6APPvU)+yo9%98=XWNoXG4H89I-hq59O4E zjZoZXGdk#QSQTP3 zV+LKx{YSHaNfVF3pu{_HL$Mfmw$3C)*U`{$e7r99m~_47Bq!zO$PeGy&i6^_rRi;x zPOs)Fz29`dz`Hb&;BKl#etr`x#88q&NXBdNT!{$@lJ>f4E5k^)IkHP-0z7}I&9x|X zw$t0X-`Kt|_v@XO_a|$@

*7y%UwZneU>kS?n;%^iO7q1}@jF`~IE3^y@Fah_@Go zN|q03UR>xO;7mds^UoLAD$uggK16!)zA|wW((P7g5xtByPyI)0?)LiXQb+ZXghmBr zwpkoWhAX1)*+Xs!=@h1Tq4K3lBR}J*^^zoQ`5)q3v!BK#-PMGKPx$)}sP25Ae$zy8 zzGVou#Gi2dJCq;>9}Z#!eNKvzWq@*P$3yLfyXqUGkPQD^!fT<71?9_Nfo{Pnp^~i< zfYn%~@KX7c{1LfKQ#zV5X@$njwbk;Z;bwm$-h@oXR;_>TL1YX>;FW2{fVwruBcYRa zgfG2ha!_+kLz`E&n85{9PRgsc0NcjMcbDdGjPUy3AoNTpQTI)mO_!Tu)0(a6&6WA1 zFCq=Hc+BYd%8Q`8qljnKfGLIVtxYRob51_nHgeL^zxFs9q1xN@H<=4W?v@FEARvAt zN!x>=bf}s0KPU`kPlnHEFme_MJ|x!aw!h~`TQ=uy_mR-x|kvus8-7=Z+;cp1kZUSm}mZ_XO_ zR-euElF)9-Eovo8?%#Dm~9hd`4e4R=EkQhChHd$UMq0neT=0zgFi|xpn z7&R$77Nm#=sE=uHwXT%S*{%v8?kNO$+{73G!cHD7;zx^eX$R&SyXg^PJcn}uPVvJs z>ZIgSv~$}N5WiXQQ2wg+?1huBX4bxVh&^K7{O)t#W2Ry zO${DI(vK|hATo=+dh4Xw-oGm+s&o#aau!qOzR7uZm?u#TP&U<|K z#?2Fk!=zTiFxF}6Tms}#HC?REiI1YAhYnC+mC(vVzB1nI;op9Ik-am`0v$;V9v*6ZHDZMS?{BnA>&0k~dHUKZHj^41IEd zvv?~id?oAf2392NdRX4p9G%*sC9(={-lK^|MtO|99}Jjq6fo~XD{o26PpV~$T%n3L zyXiAr*Qt$)lHSZUFoBGbxj>4U=5^oVrubE(gC}1VX-=$vy1$hP7dcF4Nx=uE7GLx2wK6jUiMebD57m6 zC$vPk@ToYLA>ZT;_+TTp4(T`irWlWpjf}Yceq)~>eY2EnN$K(VyXCxbHDoMR>d9Kb zGj9wlF-hetl|FQIbPNrvY-j*1Tksguh{i;;wO}}wZ+7LpCy>~tb4z5UXI0jx9NvXW zx0RqJqosFKTxTzI>a-I#rFXKEJ*SMU>k5Gauc4;(=xWgp4r{~OfWRdx{^YgEVKOHK!=I@Eb%PF8C z&VM$uex3T9S`DipMFOOT zVruSVFr1oF(0z#O=<8mv$~z8@1*(O)j{f@AD?j1$N-8m41C>BQ8c?;)b*q+$@;w9f z-B!GT_#a5AiG*1)mOvq(=+hIB)_}6ucwm4xZ zc0j?Q^#wk9!$m0;PS#C56L#1pYo_q$TW>>s>*ed*KHqfw5^c2h??F+4zD6dLgNm0= ztda%!)YYSwQvljhCDPekWpSg*@fekXlZqm`;K~A>Y(lDby9AUV${8R~VECgG@4FUH zA}?!%o;Z_wmboiM&xi8<5=>3tpSYZNTq^uBmOVwwqGmQwaef%__U*hI@iZPZBe|R5 zTNqbPa3HZ@F&;(u16yb}N%V&%PEG)Ns}=S~Lj4$BS7mWj+qvU~*Pan|N|;*VL6|SV z$FW@iX(?o3Pf2FXwdvnMMa(LVpFt40J0qIudk9o?jVA@bV;N%r=(`!r71CVA#DrtP z?wX48h)sIew~GRk%$$Bm+=)9n%pTsY&f~?%CU6t2tVG?^`nFB<0ha|xU(TaJ&Ef&X z%kfeO8cN2nC@~TXce1XGmsgW8&f`-2U>M>&KEvfwuU-e;*==ZQ%uWe}Y$Ovap*7cJ zF&vsc)ZAX*z*K?a9K3TVi7`Egz6W8#novH225wm-X$4|!ZAG!16oNE^UEyUB!a20{ z;7*_x%P*4>qaGEoN)g>Ts3ejFy%pw0$eR&k_}ccj*>8kHxvI!Y3dY@|!;9?46Os|6 zAM_C}RudUwBLdymhX}e*we;&=Lqo&NNsE#qp%T$2Gv*OCY>DfTq0NA4i2v{`eQ#Nm>j}o^b!YStsO)wlWi3gB_o0AJW zw4s6ij{zIiV)vy>V=35ChFkGJI0&By#3S2C_&e5141kNYXP(Q0zCQkaSz|M*ez+ol zcjD@kaFAe!&v%#v2r_&4@!&OA&*I*Q2YX)0t zbQHCpbzq9bXW<(oVBB`9&hUIS5kAgl!q(NolyRJ}#Ed?aNZD#2w@aOp^Mv+8F8)POW5nOuTs#`3j3gmn zF=-d|Yf1hwS^Bosd(VUy7J^3a<-8dKC-}O2K4jMkg_?8k7BhkR*9|OB;3EuVD^Q%1 z&CiF@R5%)99Pz^O!OzkQIss|k1lSw@zN)bqZ(10kdP1=SG&$xYtHEkG-DaG7`S|!( zUJmw-e$oeVLGcU-eXcphZ#POZpZ{%Ogd?Sod<5Lk$km9ge_X)Yb0mw@)7#tI)A>!J zP^iQaB3fKJr_(kl+=?%akA^d%IzG0oYyb#DnR4^Ab$KVoBk-NzZ`0y4U5XRHuD?|Z zDfm_s69btU^h|jKdd%!m6Q>{?De|7GR;A}+sp!jqlkR`XXgun`p_`1??OAC3Rj&TJ zGsm-fX7)_`y{F*~9Lh%#XrFidI5EIxgc=n_m#%+-kl^+sRvU2=(4tvw>P5<~Nw?QJ&GR5uVFJeuzI+1-*_a8%;>)!(sLp zGllWVxx1Hd|KihJwwQeq4l-aC&xnZMAc&%>>b2qimuvi57}j;5zaed58t*s*3zLJ% zZ_3lwgOE&Z1$RcJRf5iVU9RjceVte4zBU(|3m`QkShTvg!bAX&?=F+D0u^s&e2Cn1 zFFx&`_>XxVUH(>see3FvNVf={pt<=LReo@Jo`RVnd@`Io0)&M;j2Tq=g2VUW2VJ_q;2mYNQwl-V%-;ojbgVJ?3OWV`@oP{6v2)5K1OZK(Vz8l(Br zcv7#OURC;B+@0Qv=&x(I7*E&ab^SR11!_;@_4B8`KN^HtiVOIyY;h~Oi0YE>B?>?| zd*VFO#CSGOd~D}$8w}I)moOag+~L=MO%rW~C)u#s+`>TXrMpo#uXgv&Y7GtjypWx& zzbu5=KB8l5h+^o#!}BGt3U-_vf0O)NV0qDv#!o}*tCIs(+#GmedCq3Js z?tZfFr_9HW*0TlIey*0i{{F_cmn%*?zmz)jlsm7Ctk+X|?}z>N>}OStE8miM_wH%+ zt;&gc+@E~qVV$qWoTl=H{<#?q!;hD`X*6^HPKA)?aQ}aQ2 zjL+`0{OL4h#oiN+7=GpRvr28s{l3G)$+M2SoDN9$D9d&70S(%4S562w~23?m+zHSb@dca%cCv8-}^H_-R+a-3#a_& zeJAiwBeBHYS|awnC2pSUMfRxiiPGyHciz@Uv%TTy4+#+`S{IJ+X}08z@3LuL_`du> zJ;AN6(`pMw2D+0N?-CR%*zk)Fy3(q+*L)SFccth587_a>7MaPKjF?{Xg_9>TcjO=B z4SbgLG2^4*#OHax${-;h8u;T&~1u;y!bSyw?Eiser&ns+I^PHKYV>UGL~im5cB;p zYR@|iy&_j5t7uWb5A}l{W&U&YcCh0_0c6L6d9+zRlkeJmeAJOi|LA=7igfE?H71M( zGtn|XWJtp^9~#)4dtz;)TOX@m+kvQ@oPtM-zp;*vNYtzE-Dz4Lb^Wc9_#fZ|QP@x1 z>-A_EGn%WlS!f4c*Zbv9LWv|7r*5TA{`9P+!~&jv;SLUh-f zd*DwWgv!lBm`T9Mz8sm+Y4Boz4_fN=|6 z{3f#>CF3>h0v8TK2I*jDk|}8Quz`m=OQ4c5At{&Vy#OhiP;|sx(R^4q z3;<-gRjJUXZ9jDyY6zRP`Iw13Q;Sw-BOgP`!JRgJJ@aq}Txi}A`dS~Qp*o|*58QY;&Qn;Q2)c|bm z)eg}Jw9047#3oddXMI&PbBN0jeCNpOg+ztbY^8acalv1(s;5NpT}Pia=Dr}!Sdx}g z?sgX(6ol4IIpcIl$Gu>MMJ`oI`T`5{nsfy?oYkm}Ol34741qJY2L*{EkjnUX*Zo$Z zSY)@aU7*hT!^G@5hsx<-ivQH0|3mfbtjObtIY2*A=|oXGWi@ruzdh(!zbM^gSAg%H)Bya?+YU?v5f?p_zGS?QGV~kFS3>RE5wUr+tdv%Q^5ff8*}<2^uQf;Ugpj zNA$|g**fD<5X{M!VnvSrDJrUaUq+KY)2lvNuF%QfXaXZ{T%*)pj4%v1fPKAhixifD z`jZ7P)QZV*L?b zE}|>)y@J7G=uex4u7PgKB?mK50(ALMe>(RJxbveH5#w?A>Hs6_;i6DL`$nO^Radha z7v)nV$|~r>VLdyMffgS^0k0b!b({#Wf}PxgkWk^)Ze54ZqUi7Pjib*(FtQc+g`9_W zL~K@1GKq4y@gNLF;NN``@^qZ-NB|d510bgvu)^XVdGu<6D*DC3un~NhX{3*?bQ?d@ zAo80==t5vp*J@YVpspS!r2;VeBef*Tzz*S)$RNLyQObCqeMoVk`*GyIG?AXRGdnLjL3+PWUo^_l@S_5GEQ4h z;jyz-M%iVP(NUCr?Cp2m_nY$k{&|LT-uL^uuj_T~>%PyhB?t&U3k(6aBu2w$xh*&< zYLQvLat*p!hG;^(OoSPk*_-L$j!GM*P)*de7Xmwpd1tk zE=!327Vnrhd$5bT1-Ksiy<`TWIhe|IxY%?D3F9yX4b)RBt$eDN$Rc1!mjb}i#YNQX zXr09*2%zmv+<|jp@Hr|%*dvf(1ev1=(T-!VBwY(pL^lP@F=EiBoj|pw56yvk5J^v_ zQho%=@G#nJ1XIb$(R$&~V*muf8Wt~*VEb&qFTg?TdigXIJ`mDK6=c5XWJnqHp@DIN zrh|~paBd1zBUFM^4UJ!F4$i*wk_31FL%ap=V*?=hgz_k#`^T&McO& z1J+eSITSn&#a49sVYZ93tEwoJsc&0~d)iFa%uGh+e_S#n44t5Op#`)p$}ZD}lfU#v z3i#D~K1D2)4hc|SO7}rfF>FirkAZr}3SCW6U!kmV<-DIZdK@#~Yq*y&iHy=4TUWFW zWY2saZE9C$U$NBG!*+E!O35^;GR8iqwFE$io(Q=CZTH%E3+>_Cs=D&h8zN_>0$}Hi zElsxHo{Xw{_Sv4}I}9Zp>bhAdW{gE5O{W*udz7rJyW04<#e6!IFTv4E z##KN=ROoDY0=&Ag;^tBiB{jfrx_UeANV1hnAWFOu8~A_dxD5;NHo$B(-KRdzc|WN1 zIkduZz!M=~nl$=VVfcG9V2W{VZ;ZjOIwn;eAVI?h>x!Qxt1f-ab4n8u)Lj-0^;J;H zVS^kRDy(8_fk_PC-|7+d>{kA8f%M-Hi4l14~A7((~kNuvtW*9778uq8$5cwZk$_g(&%nQ>15@I@ptHyNz|qQi3KhP zxo+KjJM(;t>p}@m1hm00MCWw1D_#`%VO;c&2~?=rz%DT^BA6cj25bOjl+c|SUK9)` zT6p1jCVdYwS({*D4c(jyrxl+|Gq`~_!YdGOzC@o<;y8MXEx}CV%YNgD)<{mN*ti-O5;?EZ}LTXD*eti71&SW~GFfbPs zE>lwZXxVLbsIM4mPM+i~KMqnl&kNDUK`oP5&}p@Aeq(6Jb`c^C9UqdX}%8@isfQ)mG!hrzSHWlb^JPI5Z}(4acQDCX;6#2Gm_LbNCf%^v>@!D=;ufY`4?C7nLb z%x?36&SVKPh9=Q@l&v>CQGc3{I2w~@*4y%(HQE*23n>LPZ}$d)+oZ3s0E0^xvk-0K zk#vG1-C&Pbqh&!^AwtZ#tw%}Q3~bm6bX~z{ACesZ2BV+2abVi<{_c&qw#kD4gir*8 z6$l7X!QhElk}-Cd3huiyZ*b3uA(8 zzfLW1hHZ#1b)mA#I!CR&e^SuoB~iW~0Usi*cr%Uap6j=M0~S^fLC1qk_ah7(!YY{h z_FKdR(iX{1@hQJqpa`QcA$Lm8CpC{xK}rd-7&;Hn@6coyOe4}CBFxbZE{iJ3B7D38 zd;oV6pvFs0N1JNdhG>4s@*cfU$qEA2EC+Gn_^2B{S#b%TOi_4CnMEr)$DIJY&8ZvI zgEA-h?IfeI!??M4LVKxDK!H}zxC4lP*LiR#O*5siMHtnG?>}6*+qb&GdL1H1Hqm(C zX4f(l)YBi(y>^6a33LVu_up|An$I~?EA zK;wlkVUnF7q*`>`td$0|0`h0A86r;#k@pI1&hREcu+c2r4UN?8f*WfkI01iQ$U73I zpFrMUS>FX93_t+|jmaPA9>^_6zMTk32*~IKwC(El7aGY8 zh(L$;=*#QCVA{5H0VpC$T*);c0%)#>(E#rT!l<;LqQ{5420oW)7(+VBod*Tdimj)97IFbzh$LSKOc^l ztCqs?Jt7beTu_Y@*Z1Dufa-RD-?a^~NHOg{D2|wcJNj$y+2I(no+AtqQxJ%p>W6MO5e2C$6%K1qt5(Gz>r5|1LU&Y1Mbj2m4R1Y6$Ovv5 z*rlkLKW>Z01E|V{NUKh27wR-f#aVTCfDzvVud|rvxkBR9-OP`}7Lgx4c9CBX>gl{N zn8&!R)9!H=?QfT5>v#}3FZIJ}tT-GQGcVkKF24k<2*T0dsBD56P+$DXzhq;)kVeyd z;|j@b2{=_64{{xBc!r0NfQz;$t>e0iaGGZrnyRKizHm0!PMOOvQvg?P`dG43F-h=Y z8^o-_Dez&^_Xl2AQ_LXHB7R1JJ#{h8S_Fz&E>^=VKMS<{sB(NKK9>N%l-9-pyOHwR z>Cw`s>X4f;wm_3q3WOV2@jG`TqGW7}9rIt#d@w+P)bDT1RaSmtpp>6(3hBL#U^f>^ zd<*`~SgFI1rA^^25HP z7s_`7#m~!iuE04Kh@`8lKik)FT70|d+7r-vDi+0J3&C|u!_gRO8cOo_!_*e6aTuM^ z!j|uZGpJFnm4W*vqzJHVD0sn5Wms86Q9O?D^efrG?%?p>I&T5NlFn}EIQ2R(p!ll= z@Kv45M{{!;phEy?e}4_#0mFakQoobij~JWZECU@JYyy_)IXNC6lPf|s57Gg!9t=I0 zAryrcMd{d`-!5Tr8& z(iuDehDZgmObUon?fo4KSbR$`$b#Sr?-l<(OeZp1D`!kmd_ga)wNEnhb$m~T7*t(Y zE)$l!a#}KR=WKRD1jQSTd0DbngHxlX!x!Aq7ml;4J^AyP0XTh++E&BFB65J)lW_pv zPame1xHq##iRG)TayJ9a9mD0UuVd+hvac{6L-&C_4sG+f!W?BkBUUoX z$A}!ZA#GX{!W@R;(`m^}Y zl%!*(05SV?+bcm7jBc5nyfF?D*w5R}`%e@bJVLjMGaj6MZC~0uIWZ%OWdU!3&KFgM z%SO+$t(O9MHW6~4bchz;P$bc|s2SWe^&kMj1+SH&@0M@F{SO4!IbmRNNPk%Qe$~iV zoa-Qtg8eLK&(qa}cpoCaB$SOMJJ=83)|rD;2S6$$U3O+5S-(vaAZ2I`LV?L0qW*^t z-#<{MH!StJnvyb6XkCg{Pz0$C6ptSGI@BJ3R0}CO+-yh`>oq%DWfmJF2DVvu{!Hsy zmK4`1F5v&E6`k{oqJx#m$5xcC4PXdKH%R?AI(QvPI~v4$a2ie@Rla}ssg)I!o(1_b z-Dh-CC>G|1@f-=fdtJg*s7Sm}Wj=(|a7iasS97vd5Vscr zBOu|3V^c>|&@eTssq&Cq^$*mk4NG<4f`fOjO(2G$t6dUp+6S?ld9bBz)DE!bsgLS6 zvIfi=TwWDG$%MFa6BKVBq}oaoFGq0B3R@jQl0MM+@k4heZ5;^cXNaYW?0NMlKXIjT z1E$a|AqdORAj4x9P-XciL;c29nQ9#9i(xBN*1Md_x~?=~0+CT7cpVLdE3;$We${0W z7M;)EgBYppPN%Z$dYmo?AOV1m9iDvj0~KECp?XoiPuXf zM4=V^E%>K2a|i2`P1QD_t-e9B#?_c4+o@>8V%Ux_T;)smw>+*;o&Wkg=6?f<_W!PT zGQXIOcWdY*p)}T<0nx}MHc-p(no^Z0OQgX3?K&9=$UVh$*XTo~0fIilh6)LAZQn+l z!U*d1M17BE(E&7gHv`5dN%W#w^M>je7 z32(adr@Zp@$In1$X;17&ADOxP?FJZ?^NUrB%1 z6^A4LoFixl|JZ*=mjQqymBG0n!~lRMkOc(&G&J3(SLHGmC*_fiOPxgH9)%gvIFl-9 zsV8Bd3Z~%)#QNoWr(DZM**jpND>b+{NfU{jG{|DVU+@ycd}B@QZBqYGT(u_b*Val! zWI`C}xL(=IfLO3K!PCVC*=wYQ`&JbqTy=sLI(_KH%1^BUn7+ZGM*;yhe2r5#n^AlUuugIBFb+0T zj|xIGWKRj&aON+Ndkr}(vw3$m+$|;zEeWc6qQ|f~X8NH4R&==ImfN1~pI<^VigtOX zKRi1^aRD#PlQ3jo6g;*6{;tBK2%9&T4+SX>O<<_vel<69W+zOQj(#3kn$S+rPOjiC zE{h=iAV@2x*z~b?+`vNOjLA!H_ndh*v$1zPm)oejck%=Z3ko7;%y#4SZy{r`fNQ@^ zt9sTn+;3!_C~LBYVC>ToSLVnYO!jiVj_U6OuVqb@R_^|W?0bq&lN|&V_|kjb<(N&k z45sSeU!6W<+jEKekJqhyg$X`JgW8C%nL58bA0&=KZx*N_dQL@q7d#2c9=10S-}0nt zVjqG>0-0i>fGqx{bUtk9i8OW5=*$pJUrC9d-sEyUfHrdw6`mQ~b9Gb~NGKUE5p9x5 zz4itpXOJ#8)uH&Azuz2!vrs7NGTL=srmh#)K%YgRIR@$}6#JaDoTNb13wuG4G;th( z%=Nyq!jwvs&@m9Pc|0mnB7yvr2W!C<9XUO8A!`>ZV-J1_dep>8>OMi$0*~{|oZr3A z-e7bP)}>&~?Dw9j=h?@3Wj1L%(u8h^8Wl z4hYmY9q;^6pg$sW4k361%nd;^0|+|57(Uc#rXJpZP#b+vg9hlQdNTk*pcXd(L@|*O z{p=l~2y{N(HD}>g3mBS)vF=T$JIw;JhF9SC`z-lzom4RZGAbgaB1{dY$4;L{-r*za ztkUHX-HumAET?zf2GYKR9uic3v{g%NU~`Wc+(rI19^wvyVGXLT`dEfl``U9D^FZNG zR!!hbaRNW=6nqR;f*PTx1;@#u6^tJl>fajc@wdx24cO17DxV*aszF;|6)^mZQwd0- z2LyzFJ!=T$L*tG9(5&#wCLZk%4@p*{O*REp8iL49oL88a5W=5TZ?P+T?%kqr8gY6hs>LF< zsDj9Z!>jM!@*ESwGFXX)V}e$z07HgNT^Rn|oYN>hQaa@dHu9nq_JYp8`%tJhLsO03 zx<=(Oa_@opkSD}l!e=OmVkp{EcP9BO;Z_0tJ}T)YC%s0gSk7$_Kw!n7QzD#1K|v~1 zzVLHIyLyN3xZ2%8BEA%p>U0!j8@a>{Gsy1>lgT2_~`sQINJw!-318>W72;{ z4!`>)2FhvBu)yZr=JVq4%@!^!)a8i8joHfN*H|{nm*C1gxKmFA{T;#`N&8JGpe0_O ziF4al9fk}eyampoP`7i5U-S(@WlLbeP6xeisErWlhvuj<#{|8NE~*@`+=WVRvm37i zQZ)*`M+oOz7b9bxvlGk66l7F&xyxI4xH$ray!88T=AsSwp9;WxR0L~c%WBW9m<*vY z9A(UX^o&_<#m-5l6D`322OeCDhSgCuLqjvp#w@q;D(1}O7o0E8eWb&BpX{Zz8h5Pl zU8poQM9E(FZ9d#KKIX?!i-90NK(jzqvZ+Eo+)Ck?#L>K@Q81`z**;X~a0&U!nK#{# zfW)?z9fuU~U$Kbx0HEE&#dCPM$gj>{bF%HIkXT}SIXfhBA|Z=^-H=-X#K{# z6_KUbADTZE%z}G?2=ps@m9@b8)s1)ujJTWa+1r!f=)gG<=$C=^M5TN%@I@4${YD+NB^K`ip@BRlTAZ_h+03K zi1Xs#ZF>lN32m+AWWSYt7iTtiaasyRmYcvP^FSoWf(Ufxh$xri_TN1lH9$8TU?Z&- z*PA~lEb}l_+yOph!Vqb>0`S3!_2M@P+Nc=AI{zBX+!x~$GMVN-@>u>oQs%sF)s#-;b8?g+QBu%J0^w+^ClhjNz;U^7WAy{JKYMVFoFw}@-X*Ahz}CJ)t35@Hm@M+s6#Faah!v5B%E3~?tKBv; zgZ5))SYX&SLVl6+5Hy>a_h^gGDb$H2Wd92Jq0 z1VxSGXOGsx_3BFe8)NbiiqRj2f-+vEU}daCGxLNzgxd7F4cX#G&=^A1f}})v%ic_5 zxKjl70FIchy=&N0%&3Ek{y^6muPhLQAdR3N2T@5Z&0EocHjGLn7JmwmP&h*eQ0x#P zvxb53VAcv2!<}ub;(CR|v>#jU7&#C(Gfci0`*1b5OL}vJQ%^74(wgbl%q_`s8irf< z>GjAzrN;sy1ne;(Z-Ru~uvJF@S(@@Ab1qc_HI&1plVpA3 zHj?`;yjj$=Zq^jiP5@4>)W-?^5dfO-KxSbbj7d+u{h&GaC! zMQrDA;|5kCy6_9Q29ZD9m4%u_x-S9_u$aexa~};qLFWXXu%6HTe6j< znCV_f+(8rJN4ki^CvRH?fLka7PDH(#H>kn*4xfJ9^N0Bw^|NCd)K4JGJHKiCX1+#ty>0{v>Cbu9} zwV3By@W3dV1d+}U2v`qo_f+}e16;Ryu=gVmVZfzAk0pmQ!ZL}UUf|*o&XKY~X&(-c z0c+Ns>PnZKvvj*qK@a^lwMNO{U19xhfluI`M%W6@3dA0S z1G@OWH^|dzhf#Cy^l^o?b2$HkuH~9dIyNCGuAA>=RLOG*NDj91m`(opf*ty^{XE|S z-D@~kdmk3Dm-vHiVN|dib?nu?i(I(?xN?SAe(!Hr{>p0V=Qs*~$)S^zT@5^QuKWUk zakhg@Fm3^eq8=Jube(fE7v_xgAOeuR)E|5Y;)6)0V4NBL{)+5*kJGVu$v?Lt^AH1m zg;`sH;iih*Kh+M&)Hp9|<5vBBFvkEK4V?4;0_cg-IfL>FSJI!`%|q~&hll_af6cKx zaMsM)gOjIn9-GJRS@%yZ0p^vjosaD+hD{!aU-*IpmpTnF$EmBGBlJ}k7ZEq`V-yE!23^a ziqxD(`2q-r#`GRV-)=GAK~TYWz_7)9?ORuFhN^hqdyHdfq?5o{oBd+ z|H1aA#(8OTIkJL%Yrto*z69ems~GZw*Q+nrax9z%0$-8{Bm(N%#O4ZItb(av}E ziouYY26#O?_WNbnU>=4#B*B2+1|nJ#ed0P53?O?m$F{l(Lrs&4p@9d(wmG1aam(OW-1P(#V~TDVki_1|1B~30r4eb1?A*@^8dopr!4!=Wx&7kEQQ31_s@Y90iZ| zTsXeSYC-pdb2rSlJ>`COX-2OFE-8uf#88X z`U@j;_T3MuWjTdN9`)hL3kiQ)ZJEFJ{eyx{n|65nExxzbTsKi)H@a!f`wa(-ag*@xaNt*W6)wMR`pfd+&@&Z%HY! zoLPKI-nisTLcG+AAFkV^g#s31~rc4<+`p{DV{KN00%^EY_x4n zd&|mN8ZRYxwa3DB@>_w&=9}WW0#(DwU3_%yv07!p+g@J#Zxt|gJ%^N;r#vTV8JB-v zy1b4n5?wfLf7*F0#>@67Y>_dhCzTvHBDv zeX>gZySRj<5$mT{Tlp2nh?}9nv$C;dW9+}{#iO#P(!+tr_9v`td@0svgToixXafTy zo!_){>#&Cq7Gcak-B#7iB}26a%8$Z<4%hc$9Av$h!373wI|$q1Pq zxiu5nwV0mvDmf*OR`$MYrSNX__Q$)#o7PK8J_twFOtzYtH~6*b^XNM%1 zdfGa$6*%{)i08j{J(M6tbTBgq{+au}zoLQfhA9sR6v8!LOtatbu<-t9m!P{81)QyB zY~QW8mIDaV(m{HjYbBFKD9E#jsdaaFJ*wVaQraT-DDtZXz)dtCEW}4CC61u(M_YE} z$DyZcf|o^hb7wcKa$ub-n>q-2YyQy?syoh>J%$cGvq)^@}eb~8$bhxDO+~< zpASZ2t@JVRHHtDj2b_q5+Zn4{K$cuZkE2i24gKgsK~re@!1~_x;#**3M|-lbPS8w< zdTt+3otH53&MUL|6H?`1I;EFj>SOtlh}l< zl~(O=a6T?4EH_p(K%CWyIu9)@h~u+@kQPLOIJ;VbJ+A#R$tuu~9bnYV#Sq|r zX*e`<+qV3|rfFZo{Ev2?*3S|#`pPW~{Oz9zI;v?RB(V%VD1aW^=NE9mWP+caQw4*i z?R%S_)&a*P#ks8_i&@x-Eav!SL{UzdMneNLxo9FmD&%iKi1boK*wl!sSSjQ!K;Op(L*~m^fN@kU?MOZR=>&f{REb=unbsWzIvXLC1`^3 z2k0ekp^*uvlD@gpf2_G-J`h)gK19MZS2J#S65kqurJx;0vRYUO z?a0~BL&T_TKW2lJsi^*003l5Yz3yel&nmUnA+Y+1qSX7Cvj!n!)=0Sw0A574q#G44OokirU6c#e%q!D*uLZy#8rBCbu7aqi*#wx8}b zjW&A>L>^3P?2Tqb^kkUqS4e|084sV?z z3Tc=;YHZMN<}T|FAKOZgY@qO#DW=LW2Mhw5XAG2eD)d{rHFxWB!K)I+lT_WO^4qi5 zH)pip2WjRULF|hDmBV?W&fW7^SetJOL3Pd?{bFg`*K&q$-{o!4HpX{juj?kz0);epv&oi+k-1<4;!nYiDVU)( z0I&>4``nB*ra*AUg@5*sT-0i$Vm~>am6kWNX!%dao~akWOs){*HohQ3$?4nU?KEe_)Qui|QUN|B8Vb&(qB6C%}7i>~+kH2?QnTp0__5TqeS?;mD{=xb@Hud{Q zj-7RyW_B$$=K&#_fbjEvh-4GQt>i-{HNnP4-wG0r67*xnl%`Q%me*97z(kRET-0rk zUNhykz5({ut?cZ!HCkd`uAa5ExlhufW-?%@owInz`vG`Z%N_@o*17%Q75z{!O_hb8 z-Pv1X1t&VMiyz=q94*~Ca#hH5D7I<3LwhyT=4VW@O7BB~$kl%5XTg39z)sjifzBT* z*5&;?oYloev>a$sxDuekH+jqJ>C&f{&pYN`eHq|Bb2oTvesYy!>nYFifX->Vz&vX2 zdm!gX_T^9iyq)^iYif3h57~+xpjq^ks0=6FV`1)|2cC;kmJm8c8w12*Z+GAJj%&0r zxFX2!CzmKA+0z}-Sk0~Lpb;;%ytU(MP~Ecfd)7@(2i;f=0Rh_I=>(s{FXQc{Jr1z9g6q--IDV7=PHxj#E!a9Bo^nBH7!6ChP489y^PTGD%7 zi>s_^kw^k3T{f(~id{BZ+d@>IitO%61jPzk-B@Z!NQ(56Zg)_-z#G>cE8b3Al>$Nj zdNK|2q^E!%MLy|cJHTJ!co4UZuXjRkKvbg1OX7}|(Qf9)*Q_>527kt*>W|YEtxbZa zT3qS|W-5ab&Z-VMmV*+(2HD^7xLJHq)I+J23lvDtg|OIcK(pKLZp^EiV?t*{sQ0YddJDu^P$IHTUyp zul=i+O%^9Kj2jiLrp$bD`~JZab`P!*o9^!@HVJtJ@uX94hnKTpvDktZCl!6)5IMe} zxNu3yT_N7j3L{RR$_QMQvAECb2HF_Pw`ZVC$9Tb5WuKzENqFYC=kKAo!UmBq_ z?7lR@sbEb?uZdXJNbw#K7UzZz?F}fSSlO}|+2s9-D_TEyQxZt z;&^P(L+J@KMT4+&xMGky+IeE1JTK=`Pd-V>^+BI|`6seIl&Rbf$mqUBIepS5ay+1_ z>+knz@t)ca3ngw)Z0gnqSdX7{V8dRXFYDS0LPGaHcF(8e-J^TX-(LmA*&8?gf!$@nq*+<6=U#GY(Z$_-I^^s zR;!p?G&^>?+rRLSSqxEgvfe@<)dG zj{GhD8NBSR>=Tjh6YnOltFxK-wz{c&!`2D)09Vfty%TQ1F+)GE2#df~_2b;xBkio* z$Y}?NdZZPRf6+m;f?#h z^yo5GH(oXuZZLaxgWj)~GT~U9IE2-?X0UgVa7Qr`vp$q0n2PO(uAB|gTX-zbHRG{K z#1Z*s``c_N(IYd{tv$%=5@(&Vn=uskuQ zwYim_l~54s8X%jPXqGaR_kiD{O1)`XSkOl%QK+6Q8F6xbvrpo?`@tmryY4(VFGIP;~GTQX2lk-h}`33>Y)m)WZ)uV4mPsEE9 z8vjaFtXoE1bsDl5WA11#&w=J?dCCo(S-2wyC`~h&co8z1lgS>G+{G2L5niWStm-yi z9a#b8h01p&g8U9#;;5VywFV*Q5O2f!ApViuT%MUd>5J8hjZ3cX7#^CLSSZ5cdFx_m z;;@jm^reG+?s3Zp`E5dqnJ>K%TZWN2;NpSE9SVAm&5ex*wmRvP4SE9@4do2r;^dc}I3Nc#7;O z&1#;=d-yPpU-Fl6Nk9glWOBEaa^<^?EoO7JDX?fxuG-g!wm65SvOfOYr1$v-A-)NERf^bRB=`4N&Lig zh+Oy6y6u$>xo2|kYPFUGjp)KkqhnsIyD3wbL~Q8wJlF{BexsMLjk3U;ZjjCL86Dp- z8#L4-(0oj8w**tqFWGy&ZhLWFt-o=z05qI`zKi0UJds;Fd_>R^&b^R^M@kxHTMNwZ ztSefNib76TCQ7@wur*6UNfM0+3hwpsUUSjkNlOJ3W8ZvhHeF|2vUSU9g}V)GIY<1u zloflX&N&<$yEs}7?uT*pPwsP!309Y7BRdm3`u(^TL>JUoNQ7MPAmB=FX6N*UeD#@( zU$>X6prycU)X*E(MjUVHSz7v0P5Ajc-%NEr9q%NkICm7#LW#@^f>21z0#RX#dV;UM zV~RO+>3fow_lFZa4LqF2=`fWw@boGBtz^@h1vC{+WN=8aa|!~BT|cNuQJ@Uj_pnpVm7`sdHH=~gKbv>nL~j25p>>X zbg`kqBkH;=VKdZ~oHbe)z?W;*x>ZmgbZpz$_o4l^t?p~io6TGB2@hE4qmBg!db7Qw zq*=>b{DMXv_4Y>?{a8gv*-pr|nFs||FDWW3R3q-qI*{!V4V2LE_oymkh!xJ~CloT~ zLj8Mnl%CIRL0i+eu2$&QYl5O98B>+5edzZ%;)cIyIkV@jZ8%)}4(gWEg=JNR@Hx<& zhwWHcE%uYgQU@I#5RAKk@7}^`6EH4&X02Bp0-|s{Sg#-DcFqAOXMWYYVxKx)N6B2H zEGVh;WmJF&xfTS66LGY{AZEjPQ=|=ypU7%9drI#peX9sbf#poEGDe;kF!FY`$Hi{N zPg_gFxe4Lx4bxzvlA%CrJN%@lYbAp~McKyLg6^y-e~2n0!q-u@iC3E zhjq#`4+_Gv$Ee0*`sLyYpc^5#-a za5PIU|8WuwBpg#@4GYcPwiz`$K8f$8Krx*5e*p?fdy;#KVuVxPT}52I3WcXPc`J$` zXfe$fd<^vpLKQryHK6a!s;iD~omcETE)M zsCH)E691PY@+s8PcPpj{Q5FeD#Vi?UeIau+0V?d^d;OcI-`7BCm(e%7Ebqo%_!WZ( zdM7*j8GM%O7SVwD`@7(?81-wS~bPXIq;Xp2xo7MNfV|l5Po|q zT^{z^li)d6(gnX7fU4wB^}X^Y*RuV6)lo=YI3RV|3yw%`-=B}3W~Ym!tuHPsB~){C zGDd;BExEJQ#Bs!izr)e!_I`-?L&I~B*URxn$c9VjjkfDkYNFG@5DMR&HH2L&)lf}r z2MW5N(SI|@$x0OMe1Y&oiVe$}wFUX;PqWXnQkRw{bHv^y6S?i+lik3r^r_A6?>3l5 zrI}~qCY6O`zIHF3q~1v=avK%`nU{qY_O&3(`E0hr11$$sXUqr(8wXWdr@4Ucv%xM~V!$Qx&OZ4BvEF4VdUKtR|lq#dC&SL#v=5 z6NnBZqrsrA+b^!X>rVi5?WjsJ^!S=+dclDWF_tPayI7uTh7vI05Og&ti_DE4SvTGZ z&*1`~nV%*_eeR5RO+9as-PD|kugxUUw(KcXG?Yt}$P{f7FC;Eb6A{2UDG2BP~r zR5PZ<^g2KPqWW9j)KYoQdNW{zwxJD2=GmAj&CGAJNGJnFX9+-AoGD-p%{UWYAEp~q$!a~ zNVRUd&ZWfz>6eD?L5*tk4=$l<&Zr{=u*z(^zUNe<-e-`!0~_^Er5_=sq*#asJ7YY4 zle^Kl5AmO{Nduz`jSYk~DUwp&Le&h(7+uG64hwT0y~!(j2j_!Kf1HBm15qxa$@Bvg z9q_z;7_gjCl5dcZM)wh zth6`V4p)%XgawDg52Bcfclcxc9s7owqi|Um>JQ3%7bKP>_>xG!9b@RYe|%g3Y@s?lEBtrYQ89`g2%^+6W|9kK4Hlx!JcU01arGUSS0c!l1FG9M_Jsr{jcLWiMD6;O^{0u zxsOd;KLWO92G4GxT)lvRaNF%9^q~lg_o@miU|C(t6t+_q8J0p9*tR0{IfN$&a(htT zN`9#zsa?EO4Q7vyr&xnt@Z#uzbiNW+O3{c3cBb1S%&^sKYUX*pLWF zM-sA5gmo57<{1}jb$DgtS7%>G-{7mDt|q9t3cV(uAg7qC`YTi!snKMp9#1adL%NR; zNfDNt>{ud7i7W8!q zu6*cHO`dyv4?X>Vsat>*)^F5?SGgc z)3MxUH2wz;{SA5N>V@E~%Z@u$9!S8s;xNA3eL52RcRH^8j^+ZE^#g1b&hHVgG^%rZzegVy0yL51scpZ zV~&+GfhCrtjd*pDjIvOZo)n9VEmqBVJF77}Ne8zD_$7S}b0tw&gvIQLNiO(e=L+Kvszo{( z3TWZQ3nkQHWz||L6z!0et}=tnxld*aiT%Zgv~=8|g3NjOw1? zVIjQhC~lIt%|M(icBhP2ZloQ8c>uZ#Mgo03WG8NxHBOOoR8cgG1aAU6hFIJ2j-(yp z0#>y0O9+1%J71ZY*dEnP8dgz^G9GAj^TRVi3|rYruU8xz_!S zb)GJhZI1CGA%)wIW~lQqBL2V*i8G1h+VLJ-+cD&w^|Qx&N!%gECXo_9e)0UwFWcTV z2_hl>D;`8b*jzpcTAByE(II)W!*sj;>fe60oCCEy9TrLB$qyyuUj z#DtJ;;yBnP1=87;pWNG+wwZF32W*I84AvpWGHvhRKhBo%oOF^1dBvo&mImw9{}xB( zgy<)ojI#i{5Ypk9@6lxb*rV|87Ld$#0F5u~&5n$Yjss}UvMqf8mMig%L1xdFuEnM~ zu(3Mq9_Twj=wc9#!t@u&$y^)aCm%8%LzE|=el;B6%!M`J$}MQhQj5s(Ax?mW=?4sp zP=LkQ*|h4X6$|2L-<>e~?)UKA@4%aY$mAlTc_)d+g+Q}ZxnUfHC%w~Q* z0-Obs$wDBQg|R^}3pLlqz|iou`){m)oMI6<)RmJ+3eg*6P|PnkhgyZC*1PCt|i)FAW60zlJSklVfbogrS=K9in5k-NaB116G4lgWm5jV1^o z!LRIY4has=+5HCV5q&2#xIIZEkk51~4Jo zY)BAp2RK+m7)Hj~aQ~D}h8ytJNrV)@+g`;4Fx=nq6yQR_4$@L3Sa&iIvy1EN%V}d7 z%GG_m0%z`q6FGb+ZUlXXNN)ik*@Fy*mUrEKshN-Z_Qh2#Iq@K7i*gNziw$zrK2y5=dfA*J4PjB2Cqg`fV$i$QMdMsb!(;6@z7*( zs4YZ&OuCu{+TOCa$TYCB*0ZU93hcr!)E#R@FOj?Aw41WTqC;76(&?hdWlIM(u2A(_ z*Z>U@%#zlduf+g(jGI~ge!aPZp1n&!;od>4o*(ZQ>F1d~mYo}McdBlmgive`k6-g> z<3wxCUgePRg*Qfex?mBarTb0pBeEry^9FLh^`0YZ(wT8Wk!5PH9tx#Q@6Q;lZ|YiU z@jcR`uS>35aOP>+jDmP}ywLRc828pKOo$yjrw2Z7Kp8}e@?;~LWkHeAZg&H%v&rKb zU2pa^OwD8p1Sn38)bZSiOB`{xEqzjvAl23d6QOY~c}%Bo1r+9U{@8;Da@QEtu2&xY zapPOKxC=$nhh|ZJVI+DfiIodcQOIf5=cr{eV8ODbdpfM>h`PsK_ zXJzfv2FF8_BQtly7jXTp?1HT35=n=|3e^nnHwLGs^LV1$vz5nER>RuT{E>-}nK3r) zovF#wWF9|{Yh=Z>Qe`#}gh9qeD$2qQvz5*NiAL+Sx-$dIT-#4W+(a1vR1pqf3&n9nx_V~wJHKsy2VJHy9v&_Ir zDa_D2U_18d;Y2nh!LrOCEF4%QgBsZcu?)~q0ZpN(<{$6Uf%`L&#`5(QTdcwnZK6RCFCoOAs zd*e61{6sn%RXow&IsnKtu`r_#I(PFZG@|vNcUrJxV?#&oOD_>aCc2CG`|SCHi-gW0P`wju{jx!XI&5^-k4F9 zAwf=-wYVBwPJ`;=8Bc%AV>A^C_}j0^j5h8w>g9YnL>9H+oCc7t^HI5bBLvz6{RPx# zcu`zEQhX|2it+8{QpL@1Xt>{&>(=`I9>=lmYY1ZK{Pre!7$;)?lK27`fLZ`(rq(p~ z#!7gH1%TV0oD$kpLdFkp7XT?LXv*pq;ief~TJ}vgG*i)I*xOlvx3fn<3C>l%%u^Z( zD@?nD3Up|`sAk+a>&b$G{b9#wyfHFVmQVp7#7YY~>kLR+Ijb`l242CT2coTI&XY;2 z{)YZ9G7Ow6w&d`g@lJVwT>$O>fNdK(Se>x#%YC2F3Z{@a4t>Rm3T?wirSGS~t^|^a zVe!2nr_NMd*|w6Gser@tsovURn%Mjb$1Mc{vX~5FQ)J2PDG_c&vaXceJc~PE*1H)miYE#3J~Tpt>w}LL80b^84U6+!+tbJ`XV%ygufU3Z*CR+089BBLsvxUFQ~u#2BVsNfp}((nuz=j~g}fOw4`qZ_H5V%R@8l7{oh8E-qBr!%ftzwLN}BX(rowz57nl|leK*}Cx+Hpi zV0AQ~i^`%Tf#|x8+&|wPHmtezr2OWyzx(g(ez@WC%vIGpcWymw?j6Y#(2uoK5Kwd} zorpAV8!v57lOsfIHP=Lhh7-@Fq@7fex62?S{7F6$+bw(}hSKu&1qD<`JZ+ufg$=}H zSU{5lfVO1S6~}`r6Qgh<<+*IO!zP40eNq*N2foMrvYk@&I2)7ht10$LjsAsKrw&TK z1R>6R=?4F*EwfK4`KZPq^b_8{t~i(IVyfoXRC)y2&py({^>M2AgbH--HPgBio&HpD zeJNy;3!d-IxcEOr<|G**PyQVw$IgCT5yCUZpyn!<8}jh`!(_U2ZhPqpv3(V2$EB-YS_0k?ZvU;Qo3qiHI#12 zf)~9$fusH9&0x`wYV^YSf7eOiF!OxPHUTv3fyZuNMY$Io!9saFqv=gXaa3-pb%0xrvQRsnDC;)!rq=4{cpM&`D6OGH0X`;yH!yBwdHf4&$<#d>qAv zB4h$;LbD@@{Bk{DFn_#PDLDLpSHt%H3#{MuYtXsrJrw7~Fsv+ob(EC;9*7 zXs3$lq=LcT6>C|TyWuCCy#n@-(N5GVSoS;O!sB(53!!VGO}=djdK(BIv4FD3;+;4H zLMk+A2J>%d!BPWhD4$rgKI2S(3SR>sLg&Q_&gg%W>%8yvQgsOAbXdO7%sEer1OL4! z7L#JCgvT(XfB);2D?S`MItGGKgR-{^us!OYf3Y4l+>d za4bvR2_oHwZQSFJ55$L-+rw2~XjBG%rXKiz_=;m<0f;7EihliP=pIQx9_E)YR$|ox z)v&FSwNj##1#&Bu>_FNHIQFCM(q!`OY~FSAvfRWm^vzIQI-;LMD~@SO27WjqbC{MB%wJUW+4nfgngEGkLdN@|1g?& z5V4<^WdHkk4zoltB{L{IO0bDMC_%|0<1doVZ6db$Pw?g>4t)@EZH3U(4?7j=W^HdN zokh@Z{;Z@#S+wCPHqn6W|0CZ90*iZV{sT6~SfIIPEMX;F97KvtF1S%zk#d!TGI8vd zbkwz&ot(yiCuFdelj=kJV~(GtQRYUc*{KxoV1j(}cJh_G zL~MD_b+@1)u1`&Nb*Yl&jo|tTDj6ff{6W|uZN)@1M|qVpJ-GZ9Xn;RDvP2LUnx=R_ zb}_#}o=AlNVKR9TuHBJAB&KyEO8a1$Tm&4 z8TiCbw=QC(W>1)2tQ$U{Fjy&7r+w)FIMcjk_XA2woxESF9Afr#tAp{M_|;<`aBAj* zZ*w5CY!@Fnu55y*W)wGQxpf?kb00bE zuo@rlB?!?JR#sk|iJ8Na)1RAVDKP7F$Jy=$xR>pN#bLaPQZMsEALqrno>%{x=wbZT zslCzPdY$cul9Yl1rM?$kD|s2<>*Q>PzBqOB_!@n-(Qhvk&RCiK0MaySk+ye*Feq`t z@M8K7Uj z@fzq@~FWPvax0cB5rbsbaNPsN7;?%SEqY(~ zJ~HBk;=Ul0x7pg0r}#b9IP^pE2GPR8s7u8ZAh&m`nwkvjzr3xUcM+Lvqv?^`$YQ6+ zVu+njrG%im4PG|0d3~r4)h;hVyG$>PQ z0M5LH|i=;^Bi)0B^;Kd6cgGVJY=frvlp7wd$RuI zdqCV*$W6rXlG>ro&B7`5ma6;wGa&OSOkj#5`Ud@%=&sfCK z?r!Ql>e}{}Pctgm@~9&+{)=mRg!|st8bIKWevi1Z1d3-gaCFPKdzhQ7;I=cQqo;52 zlA@ENnIwX+lvt-==-+R!ZV}#$98-??FELkSd%RZ?n;-bBJ8fR|4C9YHfu!#m?actJ zw4C`k-*?$@3BUQ(10~mpSzXrp#-h}^uDMg_p_5%N`A?SDnu~6uI^_feg;y%2vIGZO z`$HNb8krxl5U+N|vg~o*(Z9G*jiO%J)rTKV92jm&Qsv(KJKpvYI+-^U9)F1+}MPMwfqFEyKJmM;UB@|8lvo&jw$%KC6%M*Lq zDK9OVKxh=hcd{2V5suib5aAco=)Kbq-~05_$5L{$)7CkBe#DJmxyMwq|>I$wxx>QKf;TAsGTS3tKUK|qLp+8?^bCi zp|qBjfQHJV^+P=h+;_7w@j1KEb&7hD)$N`D3_wZ^k(HD+eU6s!f0-xV-;76T%UDgJ zRMv^=e2|h#OFLp~Bu;Pzj%eqQ*3Gy#29_7O#tI)x#rfIblZ8*ot?L)!gv_*_p#20h zU}qGhnDe%bQY<6YFqu#;vOpfR)c(HkazX){;mXHx<*T_72kSDf{4RkAsweGXGfJk2 z2OkY-F#Q-7@nSAgb)+0ey}!JO3D1wASh%a2wtix#+CLS(Wuju{B9=nYjbEt8nRLI~ z6eSINY}Um->18fKwf_&6`ic9C@XndU1}UzCklgl*as?!2_3jI}`VTL3EU}Uujrauz zxfoi4@|=Hc(mLO*L?HG%_Llk`OrYL@1b&t8T8oRh(l3XfoquMux{ZLdYxL`y^NM9O z2^iUSVq!MB*HpGb?su&X+$5|$IRKBrO@)Qew2dP8liHSad~!KY1Omas+qXu^IuP!N z!O9xRS&A3kkJvf<)Sc;#7-nm7&=nkkHHt_`1y5N?h)fSTO9m@$_*}N?hchk8Ts-!h zl&ZLwiNtjv0KY(bG8l2vYo%nFVW#OVAp zXVNPzVQ|01js7h=`%R)(8ww97up^#Zqf*`9pmt_U)As3Qmz_7^Az-^Vt4q+pw0nsS zbWE_Mq5`TeVA9Zc$$dP)76?KP6*~4EZMOGOwL=-v_OcJp`=X6eJG$}O(aKuB)1C~8 z9|S+;kFYf+wjhcSKosqTX~5OCbLyaykpVmE;1Hyn)%zIt*V^ov26_04$iwM|ubDS~ zrRb8$VhW`MxZv4yBbN@YRmG|>V1YT}qA?FR6UsadN80D`d@jd9DKduonjYsX5owr$ zpMBswkxUFH3e*)!YhL8zCTZwfFgc&9dAVpe77;eDCHkufqnpIX$r8q6!!9me9R|!| zuJobk$H>}HS4$fwN}qy-c00HHkKic8)rtvk&w3-*D2&@U{N>YB`_4okU9MR@obL6! zK|kC$_w5{_V}cruw}ythe7C?4UBQo|@WuXE>89P}$G4>bPwpq9KH8nQfS59xVv;oh z(>I^>)%gi-G6sxcOk%v#*)?Pjn>CzoN!4zJGtS^K2ecW|4X2NdzhWc*_Nw*9^eoA* zy1lt5o4o*Gf3=1a#(@CGJR;cl!p`d1WpzNzcMvQ>AC1I%#$r+V#SqX*DNfJ#tvEUB z{QTOa{t>@_Kv{%b*WA)Os(ORGf&6NRttOd=xXRWVrzZ>3W`bL7=Hvyf^$JN@&<2asrx}^{!h=OLeL_Xhn{cV=n zdcE(L0@<7V^#B^x(;a_f6DhUD!zf^ocvHe!N(3RmdmsXRawQa}&(H}N)gJQf@0rzs z^pMeXvgi*#rhkm#6PM~P`0@Efz>^83uh0l}NNOI(GoC;d;|Lto>QA1SXfbtve_DUC zxE8#sui4~9OVrT?N6{l!xx6_#ZTssxOfrH(0Tyj1QcK= zNbtOS3mg+669ld7{6a|1L5&bZy#D^JKmml+!K=(ZKCsKtC0C01rkEiB4YvXMl9RLQ z&y6RXFk^dMhT~S(DdX?>u5aT7=7NMd@f1v8wRFC(zkk)RVH5Y>&XIuObXc+yOVD9D zY09N$qxWqAY?~^AB#cIA;hf=$^pXUz`*wRpSC9nEGMnM>gDG?3>3)%CmQ?smIsnma zA&)m^u;J$p%Nx8Wa2W_RSrswH+R@f%wr<2kJHl(0=w$OFf)re*8Ept==P-xk(597Y z8n9c9uPn0%Zyb~=giNOO$OME{I3qi_49j!VBSIr7qHuSlTvINMQ`p1U%zOjMG_**X-`Ijo%5Ou*P~I^7$kA8+(Gv$ z&T{yd>M>naz}N$K04o8hLV-og@~+=R2{Y~d{;>dMILO(Jqk@@v-VNz2SgdFS3zr5P z5X4PAz#i}b%mrQdF7*9uXHyjg@yt0Ke;7^F-1`V#8Vfaak` z=w>0sa4dPJF^r~wBMUAe-0y{0$Le{E}Z~*O@`q_G|{nZfDBuPLT4X{5TQOjZC~9mCq7U# zw%^gkascu?wI(HcMdVr6Gel?n5N2Hgg~ubkz*b7NMoIN6$c9C1nEG+B)w+W1*V`n^ zK}bUYq5D2JF9)a!|L=r{ekUxAe}}Rxu64uXd{L)TZ4PdR_v`^=YK)G-I@Ixr>0fPQ zy{@OaZy^i2G)(Be#=aq}?)!c``Q>guksCP_Q|YhdG-aa>gFK?CNcd32B@nwGTmnXo zFyr$>dhD1u3z;+RxB2Eq9%*;!Yvdp#N;7l;Argg^%`d~F_R7hw=e&bq{1nPWAvdLu zc$2?!x(sQCMGQLqSmM) zKr4!qg8x)lnzP6Q2@jgfF@AUz}?=Difdr zbM(VkyHCv9LEc{EwOOXwB+EH#euf_DbC72p6*_J@*(1#L9`;QA@T9qBZRGb|EKW2Q z;)M>6bzgaMbmA2p*-yCQ+!M9w&o?fh7KUBdJW>J#h-j^-7&)$epf}hSO5yC9-l0$FHx|@9*0Dyfv%2;+1fFCsu5I^e=p@hQ z5}R4;7Lu15H4HAC47kggHPYc+Ovule1xhZ3R^O>zO8Q3e8*#Sd4oMr^l7$)#$*5X% z-xv9q()(oX6J>#5N|_fueBjZ16n*iRjGNEV#4rtz{y=cF=JCyGofqAEufX9WNl}%Y zt{O+?P)TPcGfyGh4tGhU2Ty&a<=yRibS7#(N6IL)cQ|B*b%Ut4B<@smTl$#1-fBmkF+ zUhQ101~Ga1*|#TuLUddOoqu!nTr7>9>QxAv$oK&F9k!!b8_Y6gS*Fu#-eRjzLX$OX zsb%K`LHMG-NODv&;H#eD!Dt?1NL?gJ1grALZLl)0EkbmF)ksEAWtq9_*74kCd6K)k z4%<;Z^Ba>067fsc4rY-7!xuJqf-F1~5Pw(_AF)RKazriVb<1^B2AF4ce!8VQQUwq6 z3E@jnHs_Ta81Zv}DO(+LE7HE5CNdWHFYza2{cmxS;Q|vsmV=E3Uf;orFD4{ym=yD!n zChpdrc4oved4r`9-0U2BkcPJ8^;l?LgPpVJd!U@(eMK7^k<)WwEc$jSDc=Zo`r^tT zI%N0cmxJkY!Cb4e;fL21!(j@$>b3$#?jg(e3v_n~YLgIt}T4bSA76TdTX0o*~4tk>|4Hrx~sWXtB` zd%gUgEW*4@d)8N+(Nt|4oBvNR^?s~1s+t8LVYbCTuX zY6031{*~qSj5mkE1a;OJdaVKlyb8-Va+fcHN$WrqcGaM>7{fj5Z6YRH zC|5sx*<7A7kcBgG`v0-0CqumE^)wKGt$ZW9E+ZeIhq-FQa|O*-v-YjgHxnKu@B0nL}S8 z^aJm(?6vrO#%H|l{)wkolkvKtpmdh)Q+F`qQ^tzn-n04|H6pG?Y&gFU-%*3zS_;V- zF3#=eK8{q#vbCNp!uZi&uIssZ<5eHwQ`JfcpW5X!0e4qIi~K+xT{)3bDI`FZ!#tOV zdp8sImj1Fvauzp0nK{4HFssk7Z&;iO?=Ot%vHw*ksj{!S4G`lM} zs$Imo$!B^AJwyKkkq>NH%rrv>`l&G26pu^mD5DuXS6lc^kCr0(i5ae|6oLF&!Z*uz z`>_0~=jF_Wefx{0-cHe~yQ*vFnRu_wm@=QFYx$I);-!Z1>u3`TWo_z!j&wo*UiQvE zO)=gP)0=kbunEmt80jCx$!&lG^{IE1q2C!D?+ftsC-QU^LRg&>9q6=z!~d3Jo-)6O z$Cgm~Ej*+#1J7fEr|-t6labMEEm0<+{WawvljVGc(l0Q}4241`KIxg1#bHN7TqKI` zM(L~>>g+;XO=&JCPjho)=y1na(499qeFt$~yeDr&%rY5I_aJ7&FCThjrqx=C#6>iD z@LxK&3kNN9biyqv2pM-2V^GIIryEzd|HHMtcZY&C;@=oBX2gwjqPgEvg1q9h_P4V_ zkkkZq!a4#Mum`LyuL=UUqjhMjNw`MKpYYI_WaAm# zMtDhu7q6UYePGLO(%@LQPCHKG%9rNpYuUlh9*WZ`i30fQf@ek5vrE?VozpT*Aq*u! zAdi66{5_aWTbeoMT#zrCp1m6Hu~1Hq=eO00e$Xm3BrQxziGGxhUV`u(f_c(ERJ$#2 zOUJ!ek^g<7&6cccR-0gbo=|dG%Vy?m%ZQ-F4T1ph!)Uh zFlw};j#2^cbx0O5PezczkNMie+v2Zl;sX?Qvk}h|!?Ua>R3j=Zf+lVnv?&qVcQ*~t z!i%Mv<@qzER#_eu1^Ba5gRm~tio`Q^F47IiqHQ2gyO5{T5qjx4S%gC{jmy5n*^*QI zY_`!aO~E?LblfLz!<4;%=?EB;9mWXW`>}Pn8+y zjXfDo#d!Idp@pB2~gMa_8k0TEjE}4fXOTHqKrJVctSHXa)?Szn9N}2dsbp91Sr+-8R?Yb z4yGwL5wZD{tnS(ihTY)Ah$_v+Th^GSuO%Bkk6U!Quy+&|n6_)Y-uwWFm8*~y9YZg4 zjNu3l1mhM;f5gNr1p|vJapa|e#AgsBK0I9HgI%VAqbv)~#khMA&t*kz{h5*C6k{7g z@3969S=<8-%B@qT8X-^W=Le!jJIKvVLSv%&8q<4n+Gq>RL)mxpKxRzQ9Aj&x6Gx+W zSOI8ZI_SdN*o_*$TvFHe;O6}!VJ&b;zg4g=7hIKPRIKT;3rh7-E5I?^Ba5|eertCi z$|Hz?O6MHSR?+2|31S-8WBIPN7pKy^iRW_WIOxvVrOU4q%z0ZSImZ6FQd0>63hyZ$ zIZ5UH;@79(HgZ4|)3u7EnqQ?|=@hbiUirAU{S-x)8!K-t1;vi|`2;M(IG3y(cs8#u zw+qap0A*(R4JoD^K!$@`oPh$A^WygFIYR- zy>|}}R-1umVI3K7mwc7K;$RQ)l%XIc)|vByX3DQEX-9EjlU7Z#s-2jFi`@b}7y5bk z+AfU9>cMXIAki2x_?^e3G=FDy)k->h4?cj+2uRgT0Tj3_BtxI!{+ zVdV>0`4Azsiiedg`Xc;VY@yN71HANa#op!p$h6RtswHP(?ukiFhHjD$Y97&$#rhcs zWZD;C-E<>nmn4$VO@E`@s+i)rQP+A`$+Cm05b1{4gTp<;m0xMh7(ZU6}*r8u9 z=;}nLldgiF);U^oi)gKn|6%Bs>2ihHXk5+%^g9ucs7ylumj9u{CQm~aNi{)A6DgQ> z!h`Y2&z!lPLEbK(KewPRWpf3)1VT(%r+~`uG<1VqA6%zTDat9i_-m57D^T#hsqRRp zZt90js=XfkDm~kIxJoR3_jLNXL@_^qJ{YA<#iD&a&yP5|PTPrWTM;Z!2~8^POvF0O z_`0sSKAvA~CVb+{b|U4tH&XY;c#&zafC|jjmzLuaoZQ?AC7h%@l%LBh&SYE}Er#|Q37_Ij20@JD<`yG!=`o{;`5v{c=Q z|BAatVT$Y3>6?F@@4c!vFq-QyDY%X5V9)Wxo7DDbkMMXH#B-^)+L{$7^o{~6y%kU;Vs67x`N!oo08Y=X|83fxZ?B# zT|QUJP19Yi!3C@ml%l+ni+kdp1JN@mqhH2A%eFtxKZJ-~KV*lAunrrGuY~P}0 zr*z52tQ`HCP0=;vzIe0ixiS^l**kU2T&uMw1S3n5og1>|9!LY~ zU$U;uY#eo8DKS44!ml$}Ap4%M;)4S;>$ zaN*ffwE*X!+*Sla*Ya>;eL)%q1J5z&X)UP@R!_+OUbTx>t|hXyv-n2s3i%`VGd~%v z?#Le7AKu$IudjY^BDjenCF_}Gyxcq^4mpFK&lPOl(VGiqsB_!zquFYr+E%lYj0OK5{X&&YN30L;cQ3LTf8&Irb(+}&FgI)@)DZd)@_6|8AnuLMhe zN2(`N%QYl3K_vU@WRH9Wt?o>;fpC`cgaj{kv(lgxJt58{S&(bFefP- zGq3)yGTY;Pt@?kP!C(VIYc}Sp5|i5WLhlR?#PS_Bo%6l!5=orPq7%h}$Va`Obm)^k zR(ECp)O$42Ei%37s2Ht?IGUDN=OAC*)~|K=9Bs?D$}EXeUh;=hTnN>z1&1~{ z%>72lD|wQC{3GW=<0ar`K+Jl9Ozt5n!cn(;=VU}pd2CM zl$9GVt4$}p8PaLsoe0tJPPEvcnDKP4I$vpebF!G27Jjw1CJ*%rF z$hf;aUtn(X3ax|g`J*322MQ3`7I-)G1QGoyr4-jf)Kbbf?I2KB@d{5=s1>$LMQEGw zj)-fm>Y80~^Q{Y|FBmV@JMoMBg2 zA=+CQ{}N-uI9-VKT)f6LU#TX8t*#mw)PLhrNa>vQGNr*|8(d{5L05zU5ZhnBmAL;& zxXdSmS$fKEgw_N;>SDJj8Na!*Ig-+SMKJu80SF>DYqp%9_Gl8{OR8sVq#b)IiXr>l|LRG z?b981t8Rkkx`djsa-tvfBtwGFbs7EP?9;ED&tt~Q$i$Dok^mHs)yztC>aX{2^`8*^ z9CV;sCcfKy^}PjOdsdCyimiS?sl5R>ltvJ8#Fp0hy~zkQJP)J2Gf|Tz1!<6|Hx&Jx z=Q9gLC@kty{6xpD#G+)$xz`Q7`zMD(Zf37sBE9<~hdIfq z&9L6@e@BtZO+PGnCcwaWS!#t$-X9Ww@AKDgy?1k|#GHj!?3UQ684KOMuzB&ju!gU( zEQ$1IoTohkl!{{rQ@N=oNxoMUU)4(R-K*T_QEo9N{& zHFjUJsk7BGTv|4`D>mfw^XGLD>W0%W%Cb&({iU$l441PWsolG%$vB4{Z;6F?FpC$Z1WeE*-MWJ*= pKxF&o4P=<-|EYKX|Gn>^$QIdK5*^PrKP2X0`xb-EaT_hq{XYifvReQE literal 0 HcmV?d00001 diff --git a/docs/src/configuration.md b/docs/src/configuration.md new file mode 100644 index 0000000..5c7deed --- /dev/null +++ b/docs/src/configuration.md @@ -0,0 +1,118 @@ +# Configuration + +Configuration for `yardang` is driven from the `pyproject.toml`, either via standard sections like `project` or from the dedicated `tool.yardang` section. +Each option below corresponds to the [Sphinx configuration](https://www.sphinx-doc.org/en/master/usage/configuration.html). + +## [`name`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-project) + +The project name is taken from the standard section, or from the `cwd`. + +```toml +[project] +name = "your project name" +``` + +## `title` + +Same as `name` + +## `module` + +The module title is taken from the `name`, replacing `-` with `_`, or from the `cwd` doing the same. + +## `description` + +```toml +[project] +description = "your project description" +``` + +## [`author`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-author) + +```toml +[project] +authors = "your project authors" +``` + +## [`version`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-version) + +```toml +[project] +version = "0.1.0" +``` + +## `theme` + +Defaults to `furo`. + +```toml +[tool.yardang] +theme = "furo" +``` + +## [`theme`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_theme) + +Defaults to `furo`. + +```toml +[tool.yardang] +theme = "furo" +``` + +## `root` + +The root page to use, defaults to `README.md`. + +```toml +[tool.yardang] +root = "docs/src/index.md" +``` + +## `cname` + +If set, will generate a `CNAME` file for GitHub Pages custom domains. + +```toml +[tool.yardang] +cname = "yardang.python-templates.dev" +``` + +## `pages` + +Pages to include in the contents tree. + +```toml +[tool.yardang] +pages = [ + "docs/src/overview.md", + "docs/src/installation.md", + "docs/src/configuration.md", +] +``` + +## `use_autoapi` + +Whether or not to use [Sphinx AutoAPI](https://sphinx-autoapi.readthedocs.io/en/latest/). +**NOTE:** it is recommended to manually autodoc your code. + +```toml +[tool.yardang] +use-autoapi = true +``` + +## [Autodoc Pydantic](https://autodoc-pydantic.readthedocs.io/en/stable/users/examples.html) arguments + +[Configuration for Autodoc Pydantic](https://autodoc-pydantic.readthedocs.io/en/stable/users/configuration.html). + +```toml +[tool.yardang] +autodoc_pydantic_model_show_config_summary = false +autodoc_pydantic_model_show_validator_summary = false +autodoc_pydantic_model_show_validator_members = false +autodoc_pydantic_field_list_validators = false +autodoc_pydantic_field_show_constraints = false +autodoc_pydantic_model_member_order = "bysource" +autodoc_pydantic_model_show_json = true +autodoc_pydantic_settings_show_json = false +autodoc_pydantic_model_show_field_summary = false +``` diff --git a/docs/src/home.md b/docs/src/home.md new file mode 100644 index 0000000..a5532fb --- /dev/null +++ b/docs/src/home.md @@ -0,0 +1,53 @@ + + +

+ yardang +

yardang

+
+ +[![Build Status](https://github.com/python-project-templates/yardang/actions/workflows/build.yml/badge.svg?branch=main&event=push)](https://github.com/python-project-templates/yardang/actions/workflows/build.yml) +[![codecov](https://codecov.io/gh/python-project-templates/yardang/branch/main/graph/badge.svg)](https://codecov.io/gh/python-project-templates/yardang) +[![License](https://img.shields.io/github/license/python-project-templates/yardang)](https://github.com/python-project-templates/yardang) +[![PyPI](https://img.shields.io/pypi/v/yardang.svg)](https://pypi.python.org/pypi/yardang) + +`yardang` is a Python library for generating [Sphinx documentation](https://www.sphinx-doc.org/en/master/) easily, with minimal local configuration overhead. + +[`yardang`](https://www.britannica.com/science/yardang) makes building [Sphinx](https://www.sphinx-doc.org/en/master/) easy. + + +::::{grid} 1 2 2 2 +:margin: 4 4 0 0 +:gutter: 1 + +:::{grid-item-card} {octicon}`table` Overview +:link: docs/src/overview.html +:link-type: url + +About `yardang` and how it works. +::: + +:::{grid-item-card} {octicon}`download` Installation +:link: docs/src/installation.html +:link-type: url + +Install `yardang` from PyPI, Conda Forge, or build from source. +::: + +:::{grid-item-card} {octicon}`gear` Configuration +:link: docs/src/configuration.html +:link-type: url + +Learn how to configure your repository. +::: + +:::{grid-item-card} {octicon}`mark-github` Repository +:link: https://github.com/python-project-templates/yardang +:link-type: url + +Star, Fork, or contribute on GitHub. +::: +:::: + +------------- + +*This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base).* diff --git a/docs/src/installation.md b/docs/src/installation.md new file mode 100644 index 0000000..7fa0741 --- /dev/null +++ b/docs/src/installation.md @@ -0,0 +1,27 @@ +# Installation + +## PyPI + +You can install from PyPI via `pip`: + +```bash +pip install yardang +``` + +## Conda + +You can install from `conda-forge` via `conda` (or `mamba`, etc): + +```bash +conda install yardang -c conda-forge +``` + +## Source + +`yardang` can also be built and installed from source: + +```bash +git clone https://github.com/python-project-templates/yardang.git +cd yardang +pip install -e . +``` diff --git a/docs/src/overview.md b/docs/src/overview.md new file mode 100644 index 0000000..ff59933 --- /dev/null +++ b/docs/src/overview.md @@ -0,0 +1,55 @@ +# Overview + +`yardang` is a tool to configure the most common [Sphinx](https://www.sphinx-doc.org/en/master/) options directly from a `pyproject.toml`. +Sphinx relies on a `conf.py` file and Makefiles, but these usually overlap a substantial amount with information already stored in the repository (e.g. in the `pyproject.toml`). +Within an organization, these files tend to include a substantial amount of overlapping information. + +## Integrations + +`yardang` takes the most important parts, like theme, title, pages, etc, and extracts them directly from the `pyproject.toml`. +It creates a temporary file for the `conf.py`, and builds the project into `docs/html`. +Out of the box, it comes with support for several popular Sphinx frameworks: + +- [Sphinx Design](https://sphinx-design.readthedocs.io/en/latest/) +- [Myst Markdown](https://jupyterbook.org/en/stable/content/myst.html) +- [Sphinx AutoAPI](https://sphinx-autoapi.readthedocs.io/en/latest/) +- [Autodoc Pydantic](https://autodoc-pydantic.readthedocs.io/en/stable/users/examples.html) +- [Furo Theme](https://github.com/pradyunsg/furo) + +## Usage + +`yardang` builds Sphinx via a CLI, which will build docs into `docs/html`: + +```bash +yardang build +``` + +### GitHub Pages + +The goal of this project is to make it as easy as possible to use Sphinx. +The following yaml should be all it takes to integrate your project with GitHub Pages, **without changing anything in your existing repository**. + +```yaml +name: Docs +on: + push: + branches: + - main +permissions: + contents: write +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + - run: pip install . + - run: pip install yardang + - run: yardang build + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/html +``` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2305240 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,148 @@ +[build-system] +requires = ["hatchling"] +build-backend="hatchling.build" + +[project] +name = "yardang" +authors = [{name = "the yardang authors", email = "t.paine154@gmail.com"}] +description="Easily generate sphinx documentation" +readme = "README.md" +license = { text = "Apache-2.0" } +version = "0.1.0" +requires-python = ">=3.9" +keywords = [] + +classifiers = [ + "Development Status :: 3 - Alpha", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "autodoc-pydantic", + "furo", + "myst-parser", + "packaging", + "rich", + "ruff", + "sphinx==7.2.6", + "sphinx-autoapi", + "sphinx-copybutton", + "sphinx-design", + "toml", + "typer", +] + +[project.urls] +Repository = "https://github.com/python-project-templates/yardang" +Homepage = "https://github.com/python-project-templates/yardang" + +[project.optional-dependencies] +develop = [ + "build", + "bump-my-version", + "check-manifest", + "hatchling", + "pytest", + "pytest-cov", + "ruff", + "twine", + "wheel", +] + +[project.scripts] +yardang = "yardang.cli:main" + +[tool.bumpversion] +current_version = "0.1.0" +commit = true +tag = true + +[[tool.bumpversion.files]] +filename = "yardang/__init__.py" +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' + +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'version = "{current_version}"' +replace = 'version = "{new_version}"' + +[tool.check-manifest] +ignore = [ + ".copier-answers.yml", + "docs/*", + "docs/src/*", + "Makefile", + "setup.py", +] + +[tool.hatch.build] +artifacts = [] + +[tool.hatch.build.sources] +src = "/" + +[tool.hatch.build.targets.sdist] +include = [ + "/yardang", + "LICENSE", + "README.md", +] +exclude = [ + "/.github", + "/.gitattributes", + "/.gitignore", + "/docs", +] + +[tool.hatch.build.targets.wheel] +include = [ + "/yardang", +] +exclude = [ + "/.github", + "/.gitattributes", + "/.gitignore", + "/docs", + "/pyproject.toml", +] + +[tool.hatch.build.targets.wheel.shared-data] + +[tool.pytest.ini_options] +asyncio_mode = "strict" +testpaths = "yardang/tests" + +[tool.ruff] +line-length = 150 + +[tool.ruff.lint.isort] +combine-as-imports = true +default-section = "third-party" +known-first-party = ["yardang"] +section-order = [ + "future", + "third-party", + "first-party", + "local-folder", +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] + +[tool.yardang] +root = "docs/src/home.md" +cname = "yardang.python-templates.dev" +pages = [ + "docs/src/overview.md", + "docs/src/installation.md", + "docs/src/configuration.md", +] +use-autoapi = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..aefdf20 --- /dev/null +++ b/setup.py @@ -0,0 +1 @@ +__import__("setuptools").setup() diff --git a/yardang/__init__.py b/yardang/__init__.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/yardang/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/yardang/build.py b/yardang/build.py new file mode 100644 index 0000000..19d56a5 --- /dev/null +++ b/yardang/build.py @@ -0,0 +1,136 @@ +import os.path +from contextlib import contextmanager +from jinja2 import Environment, FileSystemLoader +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import List, Optional +from .utils import get_config + +__all__ = ( + "generate_docs_configuration", + "CUSTOM_CSS", +) + +# Wider screen for furo +CUSTOM_CSS = """ +/* Wide main page */ +.content { + flex: 1; +} +aside.sidebar-drawer { + width: unset; +} + +/* Left-align tables */ +article table.align-default { + margin-left: 0; +} +""" + + +@contextmanager +def generate_docs_configuration( + *, + project: str = "", + title: str = "", + module: str = "", + description: str = "", + author: str = "", + version: str = "", + theme: str = "furo", + docs_root: str = "", + root: str = "", + cname: str = "", + pages: Optional[List] = None, + use_autoapi: Optional[bool] = None, +): + if os.path.exists("conf.py"): + # yield folder path to sphinx build + yield os.path.curdir + else: + # load configuration + default_data = os.path.split(os.getcwd())[-1] + project = project or get_config(section="name", base="project") or default_data.replace("_", "-") + title = title or get_config(section="title") or default_data.replace("_", "-") + module = module or project.replace("-", "_") or default_data.replace("-", "_") + description = description or get_config(section="name", base="description") or default_data.replace("_", " ").replace("-", " ") + author = author or get_config(section="authors", base="project") + if isinstance(author, list) and len(author) > 0: + author = author[0] + else: + author = f"The {project} authors" + theme = theme or get_config(section="theme") + version = version or get_config(section="version", base="project") + docs_root = ( + docs_root + or get_config(section="docs-host") + or get_config(section="urls.Homepage", base="project") + or get_config(section="urls.homepage", base="project") + or get_config(section="urls.Documentation", base="project") + or get_config(section="urls.documentation", base="project") + or get_config(section="urls.Source", base="project") + or get_config(section="urls.source", base="project") + or "" + ) + root = root or get_config(section="root") + cname = cname or get_config(section="cname") + pages = pages or get_config(section="pages") or [] + use_autoapi = use_autoapi or get_config(section="use-autoapi") + source_dir = os.path.curdir + autodoc_pydantic_args = {} + for f in ( + "autodoc_pydantic_model_show_config_summary", + "autodoc_pydantic_model_show_validator_summary", + "autodoc_pydantic_model_show_validator_members", + "autodoc_pydantic_field_list_validators", + "autodoc_pydantic_field_show_constraints", + "autodoc_pydantic_model_member_order", + "autodoc_pydantic_model_show_json", + "autodoc_pydantic_settings_show_json", + "autodoc_pydantic_model_show_field_summary", + ): + default_value = {"autodoc_pydantic_model_member_order": '"bysource"', "autodoc_pydantic_model_show_json": True}.get(f, False) + config_value = get_config(section=f"{f}") + autodoc_pydantic_args[f] = default_value if config_value is None else config_value + # create a temporary directory to store the conf.py file in + with TemporaryDirectory() as td: + templateEnv = Environment(loader=FileSystemLoader(searchpath=str(Path(__file__).parent.resolve()))) + # load the templatized conf.py file + template = templateEnv.get_template("conf.py.j2").render( + project=project, + title=title, + module=module, + description=description, + author=author, + version=version, + theme=theme, + docs_root=docs_root, + root=root, + cname=cname, + pages=pages, + use_autoapi=use_autoapi, + source_dir=source_dir, + **autodoc_pydantic_args, + ) + # dump to file + template_file = Path(td) / "conf.py" + template_file.write_text(template) + + # append docs-specific ignores to gitignore + if Path(".gitignore").exists(): + has_html_build_folder = False + has_index_md = False + with open(".gitignore", "r+") as fp: + for line in fp: + if "docs/html" in line: + has_html_build_folder = True + if "index.md" in line: + has_index_md = True + if not has_html_build_folder or not has_index_md: + fp.write("\n") + if not has_html_build_folder: + fp.write("docs/html\n") + if not has_index_md: + fp.write("index.md\n") + # yield folder path to sphinx build + yield td diff --git a/yardang/cli.py b/yardang/cli.py new file mode 100644 index 0000000..a92a930 --- /dev/null +++ b/yardang/cli.py @@ -0,0 +1,52 @@ +from sys import executable +from pathlib import Path +from subprocess import Popen, PIPE +from typer import Typer + +from .build import generate_docs_configuration, CUSTOM_CSS + + +def build(quiet: bool = False, debug: bool = False): + with generate_docs_configuration() as file: + folder = Path("docs/html/_static/styles") + css = folder / "custom.css" + if not css.exists(): + folder.mkdir(parents=True, exist_ok=True) + css.write_text(CUSTOM_CSS) + + build_cmd = [ + executable, + "-m", + "sphinx", + ".", + "docs/html", + "-c", + file, + ] + + if debug: + print(" ".join(build_cmd)) + + process = Popen(build_cmd, stdout=PIPE) + while process.poll() is None: + text = process.stdout.readline().decode("utf-8") + if text and not quiet: + print(text) + text = process.stdout.readline().decode("utf-8") + if text and not quiet: + print(text) + + +def debug(): + build(quiet=False, debug=True) + + +def main(): + app = Typer() + app.command("build")(build) + app.command("debug")(debug) + app() + + +if __name__ == "__main__": + main() diff --git a/yardang/conf.py.j2 b/yardang/conf.py.j2 new file mode 100644 index 0000000..84beb48 --- /dev/null +++ b/yardang/conf.py.j2 @@ -0,0 +1,155 @@ +import os +import os.path +from packaging.version import Version +from pathlib import Path + +project = "{{project}}" +module = "{{module}}" +name = "{{project}}" +description = """{{description}}""" +author = """{{author}}""" +copyright = """{{copyright}}""" +title = """{{title}}""" +version = "{{version}}" +release = "{{version}}" +html_title = """{{title}} v{{version}}""" +docs_host_root = "{{docs_root}}" +root = "{{root}}" +cname = "{{cname}}" +pages = """ +{% for page in pages %} +{{ page }} +{% endfor %} +""" +use_autoapi = {{use_autoapi}} # noqa: F821 + +###################### +# Standardized below # +###################### +extensions = [ + "myst_parser", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx_design", + "sphinx_copybutton", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.inheritance_diagram", + "sphinxcontrib.autodoc_pydantic", +] +if use_autoapi in (True, None): + # add if it is set to true or if it is set to None + extensions.append("autoapi.extension") + +os.environ["SPHINX_BUILDING"] = "1" +html_theme = "{{theme}}" +html_theme_options = {} +html_static_path = [] +html_css_files = [ + "styles/custom.css", +] +master_doc = "index" +templates_path = ["_templates"] +source_suffix = [".rst", ".md"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "node_modules", "_skbuild", ".pytest_cache", "js/*"] +language = "en" +pygments_style = "sphinx" +autosummary_generate = True +autoapi_dirs = [module] +autoapi_python_class_content = "both" +myst_enable_extensions = ["colon_fence"] +autodoc_default_options = { + "show-inheritance": True, +} +autodoc_pydantic_model_show_config_summary = {{autodoc_pydantic_model_show_config_summary}} # noqa: F821 +autodoc_pydantic_model_show_validator_summary = {{autodoc_pydantic_model_show_validator_summary}} # noqa: F821 +autodoc_pydantic_model_show_validator_members = {{autodoc_pydantic_model_show_validator_members}} # noqa: F821 +autodoc_pydantic_field_list_validators = {{autodoc_pydantic_field_list_validators}} # noqa: F821 +autodoc_pydantic_field_show_constraints = {{autodoc_pydantic_field_show_constraints}} # noqa: F821 +autodoc_pydantic_model_member_order = {{autodoc_pydantic_model_member_order}} # noqa: F821 +autodoc_pydantic_model_show_json = {{autodoc_pydantic_model_show_json}} # noqa: F821 +autodoc_pydantic_settings_show_json = {{autodoc_pydantic_settings_show_json}} # noqa: F821 +autoapi_add_toctree_entry = use_autoapi is True +toctree_base = """{toctree} +--- +caption: "" +maxdepth: 2 +hidden: true +---""" +toctree_root = f"""```{toctree_base} +{pages} +``` +""" + +def run_copyreadme(_): + out = Path("{{source_dir}}") / "index.md" + readme = Path(root) if root else Path("{{source_dir}}") / "README.md" + if "index.md" not in pages: + out.write_text(toctree_root + "\n" + readme.read_text()) + +def run_copycname(_): + out = Path("{{source_dir}}") / "docs" / "html" / "CNAME" + if cname: + out.write_text(cname) + +def run_create_version_marker_to_be_committed(_): + versions_folder = Path("{{source_dir}}") / "docs" / "versions" + if not versions_folder.exists(): + versions_folder.mkdir(parents=True, exist_ok=True) + version_file = versions_folder / f"{version}.txt" + version_file.write_text("commit this file to ensure these docs can be referenced in the future") + +def run_create_older_version_docs(_): + versions_folder = Path("{{source_dir}}") / "docs" / "versions" + if not versions_folder.exists(): + # no older versions yet + return + all_versions = [f.replace(".txt", "") for f in os.listdir(str(versions_folder)) if f.endswith(".txt")] + all_versions_as_versions = [] + invalid_version = Version("999.999.999") + for version in all_versions: + try: + all_versions_as_versions.append(Version(version)) + except BaseException: + all_versions_as_versions.append(invalid_version) + all_versions_as_versions.sort(reverse=True) + out = Path("{{source_dir}}") / "docs" / "versions" / "versions.md" + out.write_text("# Previous Versions\n\n") + for i, older_version in enumerate(all_versions_as_versions): + if older_version != invalid_version and str(older_version) in all_versions: + older_version_literal = str(older_version) + with out.open("a") as outfile: + outfile.write(f"- [{older_version_literal}]({docs_host_root}/{name}/{older_version_literal}/)\n") + with out.open("a") as outfile: + outfile.write("\n") + +def run_add_version_links_to_toctree(app, doctree): + from sphinx.addnodes import toctree + insert = True + if app.env.docname == "index": + all_docs = set() + nodes = list(doctree.traverse(toctree)) + toc_entry = "docs/versions/versions" + if not nodes: + return + # Capture all existing toctree entries + for node in nodes: + for entry in node["entries"]: + all_docs.add(entry[1]) + # Don't insert version links it's already present + for doc in all_docs: + if doc.find("versions") != -1: + insert = False + if insert: + # Insert index + nodes[-1]["entries"].append((None, toc_entry)) + nodes[-1]["includefiles"].append(toc_entry) + +def setup(app): + {# app.connect("builder-inited", run_create_older_version_docs) #} + {# app.connect("builder-inited", run_create_version_marker_to_be_committed) #} + app.connect("builder-inited", run_copyreadme) + app.connect("builder-inited", run_copycname) + {# app.connect("doctree-read", run_add_version_links_to_toctree, priority=500) #} diff --git a/yardang/tests/test_all.py b/yardang/tests/test_all.py new file mode 100644 index 0000000..fa279d6 --- /dev/null +++ b/yardang/tests/test_all.py @@ -0,0 +1,12 @@ +from yardang.build import generate_docs_configuration +from yardang.cli import build, debug + + +def test_build(): + with generate_docs_configuration() as _: + ... + + +def test_cli(): + build() + debug() diff --git a/yardang/utils.py b/yardang/utils.py new file mode 100644 index 0000000..4fb3ced --- /dev/null +++ b/yardang/utils.py @@ -0,0 +1,24 @@ +import os +import toml +from pathlib import Path + + +__all__ = ("get_config",) + + +def get_pyproject_toml(): + cwd = os.getcwd() + local_path = Path(cwd) / "pyproject.toml" + if local_path.exists(): + return toml.loads(local_path.read_text()) + raise FileNotFoundError(str(local_path)) + + +def get_config(section="", base="tool.yardang"): + config = get_pyproject_toml() + sections = base.split(".") + (section.split(".") if section else []) + for s in sections: + config = config.get(s, None) + if config is None: + return None + return config