pax_global_header00006660000000000000000000000064145360313550014517gustar00rootroot0000000000000052 comment=172e0e7ed0bd503ad80208582f0d58ede662baab sphinx-external-toc-1.0.1/000077500000000000000000000000001453603135500154325ustar00rootroot00000000000000sphinx-external-toc-1.0.1/.github/000077500000000000000000000000001453603135500167725ustar00rootroot00000000000000sphinx-external-toc-1.0.1/.github/dependabot.yml000066400000000000000000000002431453603135500216210ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" sphinx-external-toc-1.0.1/.github/workflows/000077500000000000000000000000001453603135500210275ustar00rootroot00000000000000sphinx-external-toc-1.0.1/.github/workflows/tests.yml000066400000000000000000000034661453603135500227250ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: continuous-integration on: push: branches: [main] tags: - "v[0-9]+.[0-9]+.[0-9]+*" pull_request: jobs: tests: strategy: fail-fast: false matrix: os: [ubuntu-latest] python-version: ["3.9", "3.10", "3.11", "3.12"] include: - os: windows-latest python-version: 3.9 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e .[testing] - name: Run pytest run: | pytest --cov=sphinx_external_toc --cov-report=xml --cov-report=term-missing - name: Upload to Codecov if: matrix.python-version == 3.11 uses: codecov/codecov-action@v3 with: name: pytests-py3.11 flags: pytests file: ./coverage.xml fail_ci_if_error: true publish: name: Publish to PyPI needs: [tests] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.11 - name: install flit run: | pip install flit~=3.4 - name: Build and publish run: | flit publish env: FLIT_USERNAME: __token__ FLIT_PASSWORD: ${{ secrets.PYPI_KEY }} sphinx-external-toc-1.0.1/.gitignore000066400000000000000000000034251453603135500174260ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.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 # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # 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/ .vscode/ ~$* sphinx-external-toc-1.0.1/.pre-commit-config.yaml000066400000000000000000000011261453603135500217130ustar00rootroot00000000000000# Install pre-commit hooks via # pre-commit install exclude: > (?x)^( \.vscode/settings\.json| tests/.*xml| tests/.*txt| )$ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-json - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.4 hooks: - id: ruff args: ["--fix", "--show-fixes"] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.6.1 hooks: - id: mypy sphinx-external-toc-1.0.1/.readthedocs.yml000066400000000000000000000003271453603135500205220ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" python: install: - method: pip path: . extra_requirements: - rtd sphinx: builder: html fail_on_warning: true sphinx-external-toc-1.0.1/CHANGELOG.md000066400000000000000000000173031453603135500172470ustar00rootroot00000000000000# Change Log ## v1.0.1 - 2023-12-12 ([full changelog](https://github.com/executablebooks/sphinx-external-toc/compare/v1.0.0...21adcf94ca0e09e7fbce21bf87734435520169f2)) ### Bugs fixed - FIX: rework 104 to support e.g. PDFHTML [#105](https://github.com/executablebooks/sphinx-external-toc/pull/105) ([@agoose77](https://github.com/agoose77)) - FIX: exit if non-html builder [#104](https://github.com/executablebooks/sphinx-external-toc/pull/104) ([@agoose77](https://github.com/agoose77)) - FIX: simple grammatical error in `index.md` [#100](https://github.com/executablebooks/sphinx-external-toc/pull/100) ([@kallewesterling](https://github.com/kallewesterling)) ### Maintenance and upkeep improvements - MAINT: update changelog via github-activity [#102](https://github.com/executablebooks/sphinx-external-toc/pull/102) ([@agoose77](https://github.com/agoose77)) ### Other merged PRs - Bump actions/setup-python from 4 to 5 [#103](https://github.com/executablebooks/sphinx-external-toc/pull/103) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/executablebooks/sphinx-external-toc/graphs/contributors?from=2023-11-08&to=2023-12-12&type=c)) [@agoose77](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Aagoose77+updated%3A2023-11-08..2023-12-12&type=Issues) | [@codecov](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Acodecov+updated%3A2023-11-08..2023-12-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Adependabot+updated%3A2023-11-08..2023-12-12&type=Issues) | [@kallewesterling](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Akallewesterling+updated%3A2023-11-08..2023-12-12&type=Issues) | [@welcome](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Awelcome+updated%3A2023-11-08..2023-12-12&type=Issues) ## 1.0.0 - 2023-11-08 ([full changelog](https://github.com/executablebooks/sphinx-external-toc/compare/v0.3.1...9e8cc1f92d84fed6eb9602371709b6a88e47f688)) ### Merged PRs - maint: add ruff [#101](https://github.com/executablebooks/sphinx-external-toc/pull/101) ([@agoose77](https://github.com/agoose77)) - Bump actions/checkout from 3 to 4 [#98](https://github.com/executablebooks/sphinx-external-toc/pull/98) ([@dependabot](https://github.com/dependabot)) - chore: update versioning [#97](https://github.com/executablebooks/sphinx-external-toc/pull/97) ([@agoose77](https://github.com/agoose77)) - fixes https://github.com/executablebooks/jupyter-book/issues/1414 [#94](https://github.com/executablebooks/sphinx-external-toc/pull/94) ([@jdsalaro](https://github.com/jdsalaro)) - [pre-commit.ci] pre-commit autoupdate [#82](https://github.com/executablebooks/sphinx-external-toc/pull/82) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/executablebooks/sphinx-external-toc/graphs/contributors?from=2022-11-24&to=2023-12-04&type=c)) [@agoose77](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Aagoose77+updated%3A2022-11-24..2023-12-04&type=Issues) | [@codecov](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Acodecov+updated%3A2022-11-24..2023-12-04&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Adependabot+updated%3A2022-11-24..2023-12-04&type=Issues) | [@jdsalaro](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Ajdsalaro+updated%3A2022-11-24..2023-12-04&type=Issues) | [@mmcky](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Ammcky+updated%3A2022-11-24..2023-12-04&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Apre-commit-ci+updated%3A2022-11-24..2023-12-04&type=Issues) | [@welcome](https://github.com/search?q=repo%3Aexecutablebooks%2Fsphinx-external-toc+involves%3Awelcome+updated%3A2022-11-24..2023-12-04&type=Issues) ## 0.3.1 - 2022-11-25 - ⬆️ UPGRADE: Support `sphinx5` [#85](https://github.com/executablebooks/sphinx-external-toc/pull/85) ## 0.3.0 - 2022-04-16 - ⬆️ UPGRADE: Drop python 3.6 support (#75) - ♻️ REFACTOR: Replace `attrs` with built-in `dataclasses` (#76) - 🐛 FIX: gettext builder compatibility - 🐛 FIX: Inserting toctree into empty document (#77) - 🔧 MAINTAIN: Move from setuptools to flit, for PEP 621 packaging (#74) ## 0.2.4 - 2022-02-10 ### What's Changed - ⬆️ UPGRADE: allow click v8 by @lukasbindreiter in https://github.com/executablebooks/sphinx-external-toc/pull/69 - 📚: Fix ToC graphic link by @ZviBaratz in https://github.com/executablebooks/sphinx-external-toc/pull/63 - 🔧 MAINTAIN: Updated parser docstrings by @ZviBaratz in https://github.com/executablebooks/sphinx-external-toc/pull/61 - 🔧 MAINTAIN: Removed unused argument by @ZviBaratz in https://github.com/executablebooks/sphinx-external-toc/pull/66 - 🔧 MAINTAIN: Updated `api` docstrings by @ZviBaratz in https://github.com/executablebooks/sphinx-external-toc/pull/64 - 🔧: Docstring updates by @ZviBaratz in https://github.com/executablebooks/sphinx-external-toc/pull/67 ### New Contributors - @ZviBaratz made their first contribution in https://github.com/executablebooks/sphinx-external-toc/pull/61 - @lukasbindreiter made their first contribution in https://github.com/executablebooks/sphinx-external-toc/pull/69 **Full Changelog**: https://github.com/executablebooks/sphinx-external-toc/compare/v0.2.3...v0.2.4 ## 0.2.3 - 2021-07-29 🔧 MAINTAIN: Update `attrs` minimum version to `20.3`, when `value_serializer` was introduced (required here). 👌 IMPROVE: Changed document identification. The comparison of sitemaps and identification of changed documents to rebuild was improved and moved to `SiteMap.get_changed`. ## 0.2.2 - 2021-06-25 🐛 FIX: File extensions in ToC Ensure files are still matched, if they are provided with file extensions. ## 0.2.1 - 2021-06-16 - ⬆️ UPDATE: Relax dependency pinning to allow Sphinx v4 ## 0.2.0 - 2021-05-24 - ‼ BREAKING: the CLI command `to-site` is now `to-project`, and `from-site` is now `from-project` ## 0.1.0 - 2021-04-27 - ♻️ REFACTOR: key `items` -> `entries` - 👌 IMPROVE: Add `--output` to `migrate` command ## 0.1.0a8 - 2021-04-19 - 👌 IMPROVE: validate URL: Ensure value of `url` keys match regex used by Sphinx to identify them - 🐛 FIX: `external_toc_path` with absolute path ## 0.1.0a7 - 2021-04-18 - 👌 IMPROVE: Parsing `MalformedError` messages - 👌 IMPROVE: jupyter-book migration - Better conversion validation - Move `options` key above `parts`/`chapters`/`sections` key ## 0.1.0a6 - 2021-04-18 - 👌 IMPROVE: Add `ensure_index_file` event on build completion - ♻️ REFACTOR: Rename key: `parts` -> `subtrees` - ♻️ REFACTOR: `sections` -> `items`, and add `format` - The `format` key adds key-mapping for jupyter-book support. - ♻️ REFACTOR: API naming: renamed to be more general: - `DocItem` -> `Document` - `DocItem.parts` -> `Document.subtrees` - `TocItem` -> `TocTree` - `TocItem.sections` -> `TocTree.items` ## 0.1.0a5 - 2021-04-10 🐛 FIX: `numbered: true`, this was being equated to `numbered: 1` rather than `numbered: 999` (i.e. infinite depth). ## 0.1.0a4 - 2021-04-10 - ♻️ REFACTOR: Move parsing code to separate module - ✨ NEW: Improve option parsing, add jupyter-book migration ## 0.1.0a3 - 2021-04-09 👌 IMPROVE: `toc.yml` validation ## 0.1.0a2 - 2021-04-08 🐛 FIX: for nested docnames in sub-folders ## 0.1.0a1 - 2021-04-08 Initial alpha release. sphinx-external-toc-1.0.1/CONTRIBUTING.md000066400000000000000000000011601453603135500176610ustar00rootroot00000000000000# Contributing `sphinx-external-toc` is part of the executablebooks project, who also are responsible for `jupyter-book`. We're excited you're here and want to contribute. The Jupyter Book project is run by a community of people like you, we'd love to have you help out! Please take a look at the [Jupyter Book contributor guide](https://jupyterbook.org/en/stable/contribute/intro.html) which steps you through the codebase and how to contribute to this project. If you have any questions that aren't answered there, please let us know by [opening an issue][link_issues]! Thank you for you interest in contributing ✨ sphinx-external-toc-1.0.1/LICENSE000066400000000000000000000020611453603135500164360ustar00rootroot00000000000000MIT License Copyright (c) 2021 Executable Books Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sphinx-external-toc-1.0.1/README.md000066400000000000000000000336671453603135500167300ustar00rootroot00000000000000# sphinx-external-toc [![Github-CI][github-ci]][github-link] [![Coverage Status][codecov-badge]][codecov-link] [![Code style: black][black-badge]][black-link] [![PyPI][pypi-badge]][pypi-link] A sphinx extension that allows the documentation site-map (a.k.a Table of Contents) to be defined external to the documentation files. As used by [Jupyter Book](https://jupyterbook.org)! In normal Sphinx documentation, the documentation site-map is defined *via* a bottom-up approach - adding [`toctree` directives](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#table-of-contents) within pages of the documentation. This extension facilitates a **top-down** approach to defining the site-map structure, within a single YAML file. ![ToC graphic](https://raw.githubusercontent.com/executablebooks/sphinx-external-toc/main/docs/toc-graphic.png) It also allows for documents not specified in the ToC to be auto-excluded. ## User Guide ### Sphinx Configuration Add to your `conf.py`: ```python extensions = ["sphinx_external_toc"] external_toc_path = "_toc.yml" # optional, default: _toc.yml external_toc_exclude_missing = False # optional, default: False ``` Note the `external_toc_path` is always read as a Unix path, and can either be specified relative to the source directory (recommended) or as an absolute path. ### Basic Structure A minimal ToC defines the top level `root` key, for a single root document file: ```yaml root: intro ``` The value of the `root` key will be a path to a file, in Unix format (folders split by `/`), relative to the source directory, and can be with or without the file extension. :::{note} This root file will be set as the [`master_doc`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-master_doc). ::: Document files can then have a `subtrees` key - denoting a list of individual toctrees for that document - and in-turn each subtree should have a `entries` key - denoting a list of children links, that are one of: - `file`: path to a single document file in Unix format, with or without the file extension (as for `root`) - `glob`: path to one or more document files *via* Unix shell-style wildcards (similar to [`fnmatch`](https://docs.python.org/3/library/fnmatch.html), but single stars don't match slashes.) - `url`: path for an external URL (starting e.g. `http` or `https`) :::{important} Each document file can only occur once in the ToC! ::: This can proceed recursively to any depth. ```yaml root: intro subtrees: - entries: - file: doc1 subtrees: - entries: - file: doc2 subtrees: - entries: - file: doc3 - url: https://example.com - glob: subfolder/other* ``` This is equivalent to having a single `toctree` directive in `intro`, containing `doc1`, and a single `toctree` directive in `doc1`, with the `:glob:` flag and containing `doc2`, `https://example.com` and `subfolder/other*`. As a shorthand, the `entries` key can be at the same level as the `file`, which denotes a document with a single subtree. For example, this file is exactly equivalent to the one above: ```yaml root: intro entries: - file: doc1 entries: - file: doc2 entries: - file: doc3 - url: https://example.com - glob: subfolder/other* ``` ### File and URL titles By default, the initial header within a `file` document will be used as its title in generated Table of Contents. With the `title` key you can set an alternative title for a document. and also for `url`: ```yaml root: intro subtrees: - entries: - file: doc1 title: Document 1 Title - url: https://example.com title: Example URL Title ``` ### ToC tree options Each subtree can be configured with a number of options (see also [sphinx `toctree` options](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree)): - `caption` (string): A title for the whole the subtree, e.g. shown above the subtree in ToCs - `hidden` (boolean): Whether to show the ToC within (inline of) the document (default `False`). By default it is appended to the end of the document, but see also the `tableofcontents` directive for positioning of the ToC. - `maxdepth` (integer): A maximum nesting depth to use when showing the ToC within the document (default -1, meaning infinite). - `numbered` (boolean or integer): Automatically add numbers to all documents within a subtree (default `False`). If set to `True`, all sub-trees will also be numbered based on nesting (e.g. with `1.1` or `1.1.1`), or if set to an integer then the numbering will only be applied to that depth. - `reversed` (boolean): If `True` then the entries in the subtree will be listed in reverse order (default `False`). This can be useful when using `glob` entries. - `titlesonly` (boolean): If `True` then only the first heading in the document will be shown in the ToC, not other headings of the same level (default `False`). These options can be set at the level of the subtree: ```yaml root: intro subtrees: - caption: Subtree Caption hidden: False maxdepth: 1 numbered: True reversed: False titlesonly: True entries: - file: doc1 subtrees: - titlesonly: True entries: - file: doc2 ``` or, if you are using the shorthand for a single subtree, set options under an `options` key: ```yaml root: intro options: caption: Subtree Caption hidden: False maxdepth: 1 numbered: True reversed: False titlesonly: True entries: - file: doc1 options: titlesonly: True entries: - file: doc2 ``` You can also use the top-level `defaults` key, to set default options for all subtrees: ```yaml root: intro defaults: titlesonly: True options: caption: Subtree Caption hidden: False maxdepth: 1 numbered: True reversed: False entries: - file: doc1 entries: - file: doc2 ``` :::{warning} `numbered` should not generally be used as a default, since numbering cannot be changed by nested subtrees, and sphinx will log a warning. ::: :::{note} By default, title numbering restarts for each subtree. If you want want this numbering to be continuous, check-out the [sphinx-multitoc-numbering extension](https://github.com/executablebooks/sphinx-multitoc-numbering). ::: ### Using different key-mappings For certain use-cases, it is helpful to map the `subtrees`/`entries` keys to mirror e.g. an output [LaTeX structure](https://www.overleaf.com/learn/latex/sections_and_chapters). The `format` key can be used to provide such mappings (and also initial defaults). Currently available: - `jb-article`: - Maps `entries` -> `sections` - Sets the default of `titlesonly` to `true` - `jb-book`: - Maps the top-level `subtrees` to `parts` - Maps the top-level `entries` to `chapters` - Maps other levels of `entries` to `sections` - Sets the default of `titlesonly` to `true` For example: ```yaml defaults: titlesonly: true root: index subtrees: - entries: - file: doc1 entries: - file: doc2 ``` is equivalent to: ```yaml format: jb-book root: index parts: - chapters: - file: doc1 sections: - file: doc2 ``` :::{important} These change in key names do not change the output site-map structure. ::: ## Add a ToC to a page's content By default, the `toctree` generated per document (one per subtree) are appended to the end of the document and hidden (then, for example, most HTML themes show them in a side-bar). But if you would like them to be visible at a certain place within the document body, you may do so by using the `tableofcontents` directive: ReStructuredText: ```restructuredtext .. tableofcontents:: ``` MyST Markdown: ````md ```{tableofcontents} ``` ```` Currently, only one `tableofcontents` should be used per page (all `toctree` will be added here), and only if it is a page with child/descendant documents. Note, this will override the `hidden` option set for a subtree. ## Excluding files not in ToC By default, Sphinx will build all document files, regardless of whether they are specified in the Table of Contents, if they: 1. Have a file extension relating to a loaded parser (e.g. `.rst` or `.md`) 2. Do not match a pattern in [`exclude_patterns`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-exclude_patterns) To automatically add any document files that do not match a `file` or `glob` in the ToC to the `exclude_patterns` list, add to your `conf.py`: ```python external_toc_exclude_missing = True ``` Note that, for performance, files that are in *hidden folders* (e.g. in `.tox` or `.venv`) will not be added to `exclude_patterns` even if they are not specified in the ToC. You should exclude these folders explicitly. :::{important} This feature is not currently compatible with [orphan files](https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html#metadata). ::: ## Command-line This package comes with the `sphinx-etoc` command-line program, with some additional tools. To see all options: ```console $ sphinx-etoc --help Usage: sphinx-etoc [OPTIONS] COMMAND [ARGS]... Command-line for sphinx-external-toc. Options: --version Show the version and exit. -h, --help Show this message and exit. Commands: from-project Create a ToC file from a project directory. migrate Migrate a ToC from a previous revision. parse Parse a ToC file to a site-map YAML. to-project Create a project directory from a ToC file. ``` To build a template project from only a ToC file: ```console $ sphinx-etoc to-project -p path/to/site -e rst path/to/_toc.yml ``` Note, you can also add additional files in `meta`/`create_files` amd append text to the end of files with `meta`/`create_append`, e.g. ```yaml root: intro entries: - glob: doc* meta: create_append: intro: | This is some appended text create_files: - doc1 - doc2 - doc3 ``` To build a ToC file from an existing site: ```console $ sphinx-etoc from-project path/to/folder ``` Some rules used: - Files/folders will be skipped if they match a pattern added by `-s` (based on [fnmatch](https://docs.python.org/3/library/fnmatch.html) Unix shell-style wildcards) - Sub-folders with no content files inside will be skipped - File and folder names will be sorted by [natural order](https://en.wikipedia.org/wiki/Natural_sort_order) - If there is a file called `index` (or the name set by `-i`) in any folder, it will be treated as the index file, otherwise the first file by ordering will be used. The command can also guess a `title` for each file, based on its path: - The folder name is used for index files, otherwise the file name - Words are split by `_` - The first "word" is removed if it is an integer For example, for a project with files: ``` index.rst 1_a_title.rst 11_another_title.rst .hidden_file.rst .hidden_folder/index.rst 1_a_subfolder/index.rst 2_another_subfolder/index.rst 2_another_subfolder/other.rst 3_subfolder/1_no_index.rst 3_subfolder/2_no_index.rst 14_subfolder/index.rst 14_subfolder/subsubfolder/index.rst 14_subfolder/subsubfolder/other.rst ``` will create the ToC: ```console $ sphinx-etoc from-project path/to/folder -i index -s ".*" -e ".rst" -t root: index entries: - file: 1_a_title title: A title - file: 11_another_title title: Another title - file: 1_a_subfolder/index title: A subfolder - file: 2_another_subfolder/index title: Another subfolder entries: - file: 2_another_subfolder/other title: Other - file: 3_subfolder/1_no_index title: No index entries: - file: 3_subfolder/2_no_index title: No index - file: 14_subfolder/index title: Subfolder entries: - file: 14_subfolder/subsubfolder/index title: Subsubfolder entries: - file: 14_subfolder/subsubfolder/other title: Other ``` ## API The ToC file is parsed to a `SiteMap`, which is a `MutableMapping` subclass, with keys representing docnames mapping to a `Document` that stores information on the toctrees it should contain: ```python import yaml from sphinx_external_toc.parsing import parse_toc_yaml path = "path/to/_toc.yml" site_map = parse_toc_yaml(path) yaml.dump(site_map.as_json()) ``` Would produce e.g. ```yaml root: intro documents: doc1: docname: doc1 subtrees: [] title: null intro: docname: intro subtrees: - caption: Subtree Caption numbered: true reversed: false items: - doc1 titlesonly: true title: null meta: {} ``` ## Development Notes Questions / TODOs: - Add additional top-level keys, e.g. `appendices` (see https://github.com/sphinx-doc/sphinx/issues/2502) and `bibliography` - Using `external_toc_exclude_missing` to exclude a certain file suffix: currently if you had files `doc.md` and `doc.rst`, and put `doc.md` in your ToC, it will add `doc.rst` to the excluded patterns but then, when looking for `doc.md`, will still select `doc.rst` (since it is first in `source_suffix`). Maybe open an issue on sphinx, that `doc2path` should respect exclude patterns. - Integrate https://github.com/executablebooks/sphinx-multitoc-numbering into this extension? (or upstream PR) - document suppressing warnings - test against orphan file - https://github.com/executablebooks/sphinx-book-theme/pull/304 - CLI command to generate toc from existing documentation `toctrees` (and then remove toctree directives) - test rebuild on toc changes (and document how rebuilds are controlled when toc changes) - some jupyter-book issues point to potential changes in numbering, based on where the `toctree` is in the document. So could look into placing it e.g. under the first heading/title [github-ci]: https://github.com/executablebooks/sphinx-external-toc/workflows/continuous-integration/badge.svg?branch=main [github-link]: https://github.com/executablebooks/sphinx-external-toc [codecov-badge]: https://codecov.io/gh/executablebooks/sphinx-external-toc/branch/main/graph/badge.svg [codecov-link]: https://codecov.io/gh/executablebooks/sphinx-external-toc [black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg [black-link]: https://github.com/ambv/black [pypi-badge]: https://img.shields.io/pypi/v/sphinx-external-toc.svg [pypi-link]: https://pypi.org/project/sphinx-external-toc sphinx-external-toc-1.0.1/codecov.yml000066400000000000000000000002421453603135500175750ustar00rootroot00000000000000coverage: status: project: default: target: 90% threshold: 0.2% patch: default: target: 75% threshold: 0.2% sphinx-external-toc-1.0.1/docs/000077500000000000000000000000001453603135500163625ustar00rootroot00000000000000sphinx-external-toc-1.0.1/docs/_toc.yml000066400000000000000000000006261453603135500200350ustar00rootroot00000000000000root: intro subtrees: - caption: User Guide numbered: true entries: - file: user_guide/sphinx - file: user_guide/cli - file: user_guide/api - caption: Example entries: - file: example/index entries: - file: example/subfolder/page - glob: example/globfolder/* - caption: Links entries: - title: GitHub Repository url: https://github.com/executablebooks/sphinx-external-toc sphinx-external-toc-1.0.1/docs/conf.py000066400000000000000000000012261453603135500176620ustar00rootroot00000000000000"""Configuration file for the Sphinx documentation builder.""" project = "Sphinx External ToC" copyright = "2021, Executable Book Project" author = "Executable Book Project" extensions = ["myst_parser", "sphinx_external_toc"] myst_enable_extensions = ["colon_fence", "html_image"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] external_toc_exclude_missing = True html_theme = "sphinx_book_theme" html_title = project html_theme_options = { "home_page_in_toc": True, "use_edit_page_button": True, "repository_url": "https://github.com/executablebooks/sphinx-external-toc", "repository_branch": "main", "path_to_docs": "docs", } sphinx-external-toc-1.0.1/docs/example/000077500000000000000000000000001453603135500200155ustar00rootroot00000000000000sphinx-external-toc-1.0.1/docs/example/globfolder/000077500000000000000000000000001453603135500221345ustar00rootroot00000000000000sphinx-external-toc-1.0.1/docs/example/globfolder/page1.md000066400000000000000000000000661453603135500234550ustar00rootroot00000000000000# Sub-section Glob Page 1 A page added via globbing. sphinx-external-toc-1.0.1/docs/example/globfolder/page2.md000066400000000000000000000000741453603135500234550ustar00rootroot00000000000000# Sub-section Glob Page 2 Another page added via globbing. sphinx-external-toc-1.0.1/docs/example/index.md000066400000000000000000000002111453603135500214400ustar00rootroot00000000000000# Example Project These pages provide an example of different aspects of the ToC. See this project's `_toc.yml` for how they are added. sphinx-external-toc-1.0.1/docs/example/subfolder/000077500000000000000000000000001453603135500220025ustar00rootroot00000000000000sphinx-external-toc-1.0.1/docs/example/subfolder/page.md000066400000000000000000000000641453603135500232400ustar00rootroot00000000000000# Sub-section Page This is a page of a subsection. sphinx-external-toc-1.0.1/docs/intro.md000066400000000000000000000014161453603135500200410ustar00rootroot00000000000000# sphinx-external-toc A sphinx extension that allows the documentation site-map (a.k.a Table of Contents) to be defined external to the documentation files. As used by [Jupyter Book](https://jupyterbook.org)! In normal Sphinx documentation, the documentation site-map is defined *via* a bottom-up approach - adding [`toctree` directives](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#table-of-contents) within pages of the documentation. This extension facilitates a **top-down** approach to defining the site-map structure, within a single YAML file. :::{figure-md} ToC graphic Example ToC ::: It also allows for documents not specified in the ToC to be auto-excluded. ```{tableofcontents} ``` sphinx-external-toc-1.0.1/docs/toc-graphic.png000066400000000000000000004375761453603135500213160ustar00rootroot00000000000000PNG  IHDRdsRGBxeXIfMM*>F(iN pHYsgR@IDATx E3{"$W/!! 7(*`H6DW|((>AH (}_as3{L5=3;3;;33-OMu       2ziie֌D;ه  _X=/Tn#}CE4UwׯK @3^rB@t \''wo#  FPYA%  @*{NO[oYj hcO@ CXg@ ;켸@KOֵvtkb~F t{"u)e*ﻷdmmOF μHt @\%P/v@BG@q,Q5WwfQ>u@rQ;tu e/v杻>˘@@Iu#Ϲ@@BA:5RfU.RE@!ڪݡdT4VJSChC@@@ `@@q}KD(Hwؤ2t#~E䋀Rq%JIB|>D@@ #2I@@"oyi(y&$= Y=Nt(2  D@]Ji@@ TSe:|iHF@pPIr2Q@@ P75@@FN`>5Fpf@@ L A  N@],i @@ z–i @`zV~?,5~lG@@P  `МQQ   z{>BUw#  3rR2@@@'PSʬNYh@@ P_כ"     D@K.@@@@@@/uu-      KԹB @@@@@@ P_כ"     D@K.@@@@@@/uu-      KԹB @@@@@@ P_כ"     D@K.@@@@@@/uu-      KԹB @@@@@@ P_כ"     D@K.@@@@@@/o~ "     @&H{ߊf}OcE x      UH9Iget7Z)b^[_K@@@@@`K~Q\ɕI `F     @k,đfz>ǻSF*b4S^Q]4S Etj|@<5#(zޢeP'JnAcMJ} ɏJۖUJ@]$i@@@@Mʰ4Ю:i5HQ     @=3~袛$J#j[HDKfwRsTS.)7kNbKΗ:ht{κN fjH.MPϹK}# Ϗ@@@@@гnZ:@'e jq'&?.ݒ $x'  >)mp`n;Hvh@@@rXУS%>;#1TRnxu^@2&IQz~@vG.ǺA_&Xyi|XI߂&_J~.zAV&^ ɺ񒣓~۵;ijb'ezFE'=cm=Ko0iTO>>|Di\.AzVc%})V;!e  @ ֐/ʚ) y[dJKU-1='LDI`Gs=HYc{CT'2nujk~JKޱvHnKdgAJ9 {uɫPFٛ7ˊ󶔿%OS n#m5:F݅R+Pﶗ-R+js%蘒^վ +|BGu3:6$)@.I(!  @A2|e7cNh1TB@?TfqYkqIվ`+a7Ws#I \VPOJJYd=/:o}Srt3{ߔU:gEۥldhӷl⮈qR}ڶ7*+:8lTtqJ~Fz#F9tL"ʣDa"  гlP13ze䔀etĒQT]43_s=cj_ҷ"!O`tIXR+Hg]"+:v]XFY"J~7 K;t+P7FO`v +퍨]Qۉ6dgzv\gJ*Qq)F!   O@r)Ϩ>s_ Б9Էf;{^ku#p8% 0 ">K~)NK%g鴯x>kXɗkqR Yvfzx倵qb%OB@`hQ  bU0m};x#!@bE|JG@@ w C~gy%H?* ck@z?Uo}\ ފ w_{r_F@q Uk}zFm=.V3YwL`]9KbB)LFcRUS{un%g xv$(wK R)@.  We D6׀t:gJdVay@ e8  b}np>@ |^Bo{Q'=te1ݗ@ `X9pjsbt]*YhhvޥYs3~u0:}] t.:v:K_?//BwXz] K}u!  @ei us/]/7\?N@b3ngu$< }K%ɭuKo%VoH;1o_(׳VI/Jw;zooGQұ\ϚI.Rᡁ*rG@]\KF  0Rzz qN-չϥCh!1 쓒GI֟;'yd$_)yވ!eWHϙA%= :F6.|wJ({K(9^#ގ!IK&PwϨ#!@˓ 0@@( LH @;0R@ s:ȵo`t N{ded=,-2Mw䁒-S) :LR1 ]J3Rt&9seD PיQ" SJ9bF<蛰gea @t$N oHHYV*y(%9KoKoOIT&iL0\Q= rP@]^T  IC|^Ip΅#)g[ٍn }B,pt @LgR>rm+ߑ]OIlE??$orp])I g3\@@44 &@ $JgH$L K#@ZDKAO9iO:sHA%Ht;OHցcUHP%$v$+9(9VU(eyr,ɤ.ɱH6P/ :o_tSj+^$>]u{m  @Fi+ٿ3!NE -K~N!-@xɇI>TG6%?'Y%츯7AHgm jG$Lv_魡Mwdk%,Y{Tӥ ݏXi#FER`XSF3$w]R)3H 3d@@ 0.Š4:#~gԹu+/(BE4Eiil?ך@tm,ɺTC P!! dX !y4XN ֑@$0^:3.ƶs RAԧ* @@`$z$9'dZ@OSJ'?]z_ޒ3ӗ!@礣g;@`dԍ?gG@@@ k➯&0.k.E u_%YrHts"{ԹZ@@, !.BYgYt?WN!QN?Jp[/ɒ\%y?g$@E   R7\AG0(.[~n. 2"Eg;ڥW+.@W! _W"@v ^;b.}r^v]hz  Qud  ' 3LyuQ3"aR~3$+qz@@#@n0ZE@@ 3bP& q:3Cyoǹc<  @ k@@-3-L y \Pt;uKGG@@\'@u! dS8aمfp/lP1    9$  :H׳3xF\vq${@@H^@]VD@@  v(M;]MX'UGF`3+z\NeE  #%@n9/  @0#w%#IMjj%iqU0Ώ,N<Ε11r-9 M@@ P7@@ \qu3wtz0{::Q][;@.^ @u9r!   _&kh󌺡q  9{Ό   @Ȝe(D'tc])7B'@@sͥ#   OYm]ݡ"U]cjLk#lWnتJ S&c*bUtY:tg8`$ f|Ѓ;`\obYc@@ s酡[  "?slZn˯SoRͼcuz겟ݥ\9x/(w5}Tz-CTYZ2Q?Hu'L_h5/޺$4 ;o[ԄErv74S/.>턈r6\"ct$#妥Kn"/  . P Bw@@@ ?t Nd}ojmﴋw$/jw˞]=wֆC/~PT[IUzf`s[GӨtБ@]\aHN }KwR&28# Y@}C@uxI.Q`3H[V\:/S}A ekcz+{l*ۮ*+{x;Fco*ȗ뷼eXe|gbX= @@-@  N9+6SmݬV*_g̸ )ϲ[xؙB ߯W5inVvr 9WR*}@0v4?>y:ugDlp'cɆkzn7>ӹuBG@@ P@@/6'~~헻 iLJAfiOyYGQxޱNSު۸-7>P:ȃez%mO]蓡o=T`9}oT•+{o})s勆N! p*ur@@\('1k KSBUT {d6]g/+_Plq=WEu̬p}kqUES{꧳p"z!5:m@@ s{  Y g1k =ylP^Ow=|QG;E?u;cF?>nT{;#KgDB6kF6/D57f33c@@}}  @65^ۿbUQVqG} ۯˋNE86mBuH9tzš^HP.=   dϨˆD@@-w_,aӦ=Cmkl{}cWUZTt,Tu:dޡtm/D' dˊ&HrgyF; B PyYGTsnkR`ߢ (_]m@`(ꆢ1  8\yg~%x-ݶ WG[d]oZU=_:۶g+W7lDY]uQ  g+5_WGv+pFy[3-ɗJ&!@ p @@rS`vʑ XndJ4*m'=!  QoǤhcYd3P_w׳ cz$/yd;x5u% O@8@@ Uϕu`noSu:9b3 = q3)rH ҄QşG]{_KjT߯9K]yީʔxt;% Pv5wgWϝzx?楀e;jٷBnH K,ۡ7Ik֘sJpn+P3]|uM*X*ߪѳBI;U_]L/S机&ٚC=JdYOXxQCٗ(t`W]uKvFSGOd5u9S񯲽HrI'%[s)N5ԁu鲾DqeW9XE! "! dV@Ϭ3^|,%`7+:[es?lf貒b_9Ro}Qe GVvm{zL5}յ?Y݃55m|ݫEbuαv>m@-`)u v5j{o oO)ϤcZܸ`J,]QUZ.}s;qyv]6s ry/q۱:[mH>Ʊy0 ω{۵j[c+nj*UŅ>Q~1LcnT&mmL~nlGKղ>2u@]$3Q     @ Wt& S]HbF춌ti2%\Z<7VD{/jM!q     z9]7uMiβgU}0'+x_x;mJx ̴\McApDe3)dEQf @nh  @|gX15kȿ"ߝBXN~}3av²C"z7V-f8@@|#u,G~& ׀kk׀~ @+0git%8C+ay̪.4LfEu_G+he;;/@#|]2p:-yIj`l"yfOKNʖI&!@ ԥ&@@@@@lx :Tt7uon\~:ڔ/|io_'c/ir2S1"ۉN}[Q>;\U5vvܯY:'Jm8w'з,]׳<^m0M*LX] |$3]HJgԥJv@@XS3t@@U'5`ʐ^m9Zy6 LPyJynN Эk :sY mm5o KVXjݞQ= pgC lS*`c%Q|=|djɳz;dNyI ui@I@@< Ng" /Pge-kPFsJRm mӨIšeN<:h3 2$@.CМ@@ W߲:@F^㑿@@`0F@@@ u   ֗7@@@@@@-a  @~ X22ۀ#n$@@G@]\+z  2 []rBu y)ߓ{s^A# dlt@@ 2o޿gunPv   "@.[D@p׫"{KC@ Oq72Oϰ@@P>e@@\!PUjs~WtN y,z/61CG@BfeE  0[{|ۺS:=' @ P^gu/.̣a3T@@`F]\D  0rzV>׹ŎgF[@V`  (@.}F@p^|2h~Ý=ۥo^x d5s{~@ : @@ PU" U`l W-:kK~!"е@Ǧ{]+b  %3z3Z@@4 }ɆO t?N *[eFAqL @~X֭mVGCi_ c@@ We\  #"Y''^ M a`H8 ya7\u؃ jZݵׇtM&{ K5-ƛT身W/_(tƜy|Q'zrK=tb]ç۲U0t+p4n2 u][>v8I[wJ[_pZ8ٞzUoɇe%W "29Kr:4Bsc96gieqWSf}og%cf m@@ JU^m7%t}6e^ү'7ķK>tg{f'@< 10Hl 4/TyHL u0Ta;U]?H:|hGx[6PӇ]\ⳕvTm>ڃmU@]^yƍ  [\}2Wvtnҟ-^~@2-pQ,(xDfE,J{PrɯEK꟧gEgb[hSdG)dG3ڠ,IN)e>ƹ $9^v5KT&_CKC9cߕsv_?Dll!7%`   |S͚XB.0dT@f/cK.f @oQbwE(`IKRL`^%Rb\wm7^~2 5gyn'ط`BEJ5_sꚮ}<[SuGe=}b])} &'jBf gX*o[ IVސ8v 2]}A6J})m?#R# 3MH  T=1%1*U7u(ˠn\GfefݻkW'uYigRj2A1EneĺַeͻT/a/,_֟hSeᵷmX"@@@@@ 'OnyZ= aj*wjOȌ K6uFr$ԙ^)Ie|!c?~yH.._,.9'h'mf㵏F(@.:cF@@bbAgT(ϴڛ/u   =Ջ%8?gF`%pP^ Q^l+Z2gIڑ`k@;;lcI1i7X@@@>£]|קϿ|]|Hq^@@門\zZni\o*=<nd<ӭ}3#g䁏IFWJ4,or-%%ivtζko{D +#  ;=MsT@@8iIR9Vк^3NsgR[LKU;6V,+?~0T{=4Gi+\)^{{(tG:}@ RIk   @HԯUpܪ۲cOA@FD{t`KwBlGw:;Xgʶ3P+snXs?@ncT1EȓִXևMk۰y:J} +P6{o1k߯- @ =J   iǖ{[v/<a  VZSj*ogn)ðO,i(A{f5kWn'r#4}k\ cvD@͹3κTɮ[V:(>oY*ca/$c<`V@      ^`<哇].NgM SkNHt`P۹PVr$yZcS>vYPyS nSX2nӲ@ KSuLrnK{Y]SvL?u^TxXe6ԁ x,U]wc#ˍ.iY{ڜ۲>=V{Qu< X>,V-ӹoo9{2w׽*̩G2 LugWכO46n޽g{'Tb^d7[m lz->|cm-w\w܌"sBioxU-JGS X=\H  //O̯k骴hLqe 9Ẳ"Pws g(Cj*wOJ.3=ir[I`j˱WTU>zV52sdF҅M_ZDm뛓gWU\]VʘoأKv \3F$evk*o6SiPj%K=pYLeylJ}Koql) PD@@0eU4&v쯻Q}|8Ք&+7^wDԱ7>HMM7jךz;2^[vj3.Q:{GKWĮU;2aHK?UCIh-Yz'-wzsON9爞CNӳt TߜTt .uOM{j:JRԖ0z>fńBGUuHS@IK[~b YfqU̮kzY{>^±ܘ*<{=er}ܚqO֔O1mߟu{5㥑L RxA/$E7˯YWWMR}ڌV?xiޕTk_@ }gK  u]v~#.֎y\j<W8iqDmU}ingSJ(0(luRJt0nwwz{[/ѓ \W`׺?;3+Ӭ^F؜A6 t1Vz}*f8LY@as7+<^UM_I w]gmH;MoOQ~j'*`] ZG,RVo"C/.*ƪ sW;7; c_XT9]shC+p\zG^ "='IUpH4 ܒڒi6#^1x$s45ȾZeޣMKUsѶXV୹c{b]C/'vc9{Ilfh*YjQ焠i/Ak_yIrQoUǧ,eXjS~?NkOiG`ٗ?_$H.+6[TwUޒXٸcTY}V0 OU[#ÍմSm)@@ wGݳZ >.k}[VuNysOҨCN46q)Z:>m*L嵷R/@.  [vF|3OA0 pdfvwmA:]/ܳL6 BOzȿvN1HR=X{{>WC&J Xʶ z~v>f~hO{NmgKb SsUb@@ Z}G@@o7d`ʿr3+uz׫'LV1Шz\X7XtI!9wڔČgܴlv@@@0.-4  Mt=ήtШ~s@`ڶΗ²]鸝},;;  x*'DLtմ5a@>G&̊ !lQ@@H3RH   0hhc{\^Rr7V|Y|nQ#73jrxFPl w:md &   C`F8 @@)l7>.<ݾ/蝰m/sf.l-<[j z 3%H˒J9S Z9yUwy:|T:O6_['   @ԥ@@@ @k4zDIy?xÁk&vxqPfat)kg9RqSc3#VNڟ冎5,Kv=m9ϙklu@@@ "̵1@@WZj]la}bTx[tF֜u#k9vo9֍ع.rJ_&YʶbrP6V!  9*sa!  @f;vǏEmbyqDJ&:}   @ ˵+x@@τE)jWg_+F@@]'ˌY\){&1-R- gE%W#f`[駿1ך=#fVFu|p`b W;wuFihޗd3lkv(@@@ ["Qo@@*~JxK9qS}zs@oqn?U.m?cܴ}Q܉M $!!#(l͝~ga{g tg[- Mr"# "DPA45ڞ홝M3~qw?U]dg'Z{&<]O(-''}pH, &MӴvwe7/K6Wǝyk$^fyqC1NWY T..q__7uuRV__[x#  .\.C@@D4(3JJ2MCnfhg %J;knmZ6>q+m[q*Q' Y[=o׷nzp^/:);KkOOU;#/󱷢ziǚƜ vvrIAL袒3dxr/j ^0em   Y+}#   un&u``Ict&67ƺKK6>6O=|ucn_skyǝC;7>|? vyoLzQP_n~&R]488f tT?Hg,]Rza  \  (;_4T,uؚ_@Oo mcK*ݭu?&T3qϴ 5C^͛>%ѶKfFZ5ׇGvscC-{??mk˿?|$ I#϶% &ʬ;!i}ڽ?ȃ>Qx^û8ďv4?bT^#  .a\0C@@0?}wM1#pHblZqf6&T`zPEPI/mEPY٘Cx5K7joo4?,Y9Hv%nݝp6Z|a%]o6;H4jUP#%RY8ȗo~j3jbQ`؉E9c?> yI[&K;dV^@@Au92$@@Iݿ٦>YQo,oτx ŗ W$D Lápbf%jm^f%>Rfs{i:=ǶRvC@@P/E      /@._CF  @?MZMa?܌[    ef   @~cԌ@@U@]r@@~p\ޮ94o˭豀3.@@@Q@]?bs+@@U۶I߷ƒGZe ?gg6G@@@ e+H@@'R{5{62M`{uh'Y-F@@@DP  ]*팥wס K;'5Ef]}ݲ-n  t[@]  @~ إkn  붺3X3&2agS1fB   `w*Q@@6X W&3fDҢAa/VST6v ]m@@ r=  y&`!,w8Kv̰;pD 3 )F}K2;@@JQ@@vfTJn-t5G=6qW1?  K@]di@@<h ّ.mK6φ    P $  1fyܝzmՄ*~fI6;7F@@@ P7@ ^@sKْ̦7 ^Ml޳.i*   x% mJGu-5f|  Kc* N;D.+V]8GK$`Ml   GJ>\UǦ]X# -Wϭj@`Q@ =mAu[^* :sN)F /--작ߋ ֥Η&dSkv U=Y;Ffyg}wRYU J :Oև?L9^DR$6]vx?=,/4#CǍ(.6dowd$C2WFmn,{B eëD@kg}a2:@{Q#Xmfyܨ@ ߫lY;^9_Py`wY;U_3_>  r@ 9_L}BJ`_u}^E)h (%KK`@JrlYًf@`F]־tt 9voc6]N R%`/:vh7G*Il [v8_skX-z25|T)(3(K n7sI vKS3ΙjEC9V_rusVUzťs/7Z[j[f2syݣ6kW-(=76V+j^^lI7^ι&Zo6JI%e]oE~ PO@@@6v< ,n-5*r_>KXM$ݠVAn:9wr紕nxzK TRvm+I[v4{W/,جOFN0-џټjrCGV5D+ J^ge\ߒm*Z埜枀?.}Оeo-UNݵ J/N2q1^yQ5 =5ߞJyuDyX,;|d@X    2`U`o~@,$Ku+r@?HgOMY:>5cy^,-ylAA2 lh3Vt_[nuݒ敟iț)/AZF# 0zn    d<ՋKxt.Gv\=AY( ־۵2g~]zpSN/ut镁`KDeڴ<:cő]}?W#@ oq    ^w $ǓxʱT`yNJKoNͨ@sq }<>5U~Bob3#z* dԺE/Fݯl-} #@&+      @lTRI7Ґ^uROIT_:9>QW^~ -Sp+]N3Vȳh}ߑ1*@ 3e@/@@@@@gq>\\VB"YVCTەQo&vd޷UOQ (?7G@@@@R" SHҗ{zGջ2nw˪?*nU[N߰i݂rK/J_"evFe)U:G )2啠     @\tj/O~6my> E69>K2<]:M\m;k.xl G .cBv( ub@@@@@ \?Gh! }u*Juʾ:(5n3F?=sEO]RTsT)ZjFK FC*)XnqzR9!@ fZ     @6!N+] ?2t-2o,8Mk}ͤ5S[ְE pәO9P61H97Fp#/YTqa1z^+ !@ ؗ!    !0ky3F'mkEvC Ihh{qkyi¡;LTQ\YZS$3嚔1wVLjkٻQ^"9ފ9>z0Ev|id:!@?e? s @@@@@ ZMs:D~ XdmckXI}PUTuMac׾psҐ*\/+FF[3J+*V]VcB:?s>Lh7:U .q"2v,^/;?tό֕^q[W?We;&$۪nm@@@@@O@BksYRiypLEN@Ax1A:Yr{S̵i:UU_S, $`UѷsΧi֊lɎ,+WΓR0g,}-y,uz@@@@@ cfwW%pTN%K/ˮ;k^/.k84aZ)v2 j>!{Y5 zv @8C@@@@X`沚{2ET;]էXqdWz2ӭ+Ypa;N7EMR/J27E]eFrq^6&zm sYw]LT:]jhUs|/e5vn$@w'{(k.ٟ]8h6s$:WgWU5yG;DBE P@@@@w*ez,U)j^Ʉ~ҩmq`M&̦W"     y)      @6 ˦W"     y)      @6 ˦W"     @0gF@@gTQo@ 6W_ =G^/ Ԑn{⎙@@@\ P0CHf2zJ$HdžE`+M}->]6juŗ &|{ӗvy @@@@MEE@|\FGgSV/%^?4 iט4ݦfjlVwh%2/:Kuy1@@@@Ku]Q@/׌w/:۱!(*8Ņ$pzҽVwh Nmo1tM~/<;`]   @2ut8 HiJ:%  OG?#S$"W&ogpZHt^nA   P`ݪ>@#1zBWχ1f@@@@?6}q:_Le9Ge^]rW:2_H3#b t"@@@ 3:Wѿa ?/3U>w2\  9)`|:' q?rOdg[ ;DF@@Cb@@p]<-T.2|c+H@@]@f|{}y%p gװm Ə   Y#c"F>aLaA[x݄IqzvӮ3"XX\ap6|_i&9ڜ:Qdרnh ;^}`n=J|/YNt$;56H7W$&J:- m^mK Nϕ4NI]Iے:l|Z P/#@@@@\ȇas@aIM/UP8XRK9og]` lYOs;qZk=KETʱ!O2뫶E|u,y|c_!r%,.Otq|oIӒ{]}J IOK>/?"ǷHZWZR%-?1$@.^MƂ @ C!yf|k5;JcTHo`ijjTՇPjĈf/oGǭ}2 Fehd*ҢLyw)m{֭ptTaN0$NFl}{wFUQ1B:v))^n/% d@>,c̪::@:-b375JZ5[v@ls# }Jnijh" Mw/7r}[1rHJL<plER闒>֍ʧKJoJjhoD'ʮ%-t_ܹC]ҋ'8F  V.1! @V zՓ88}:Ms?87QS]>]Û;yCfJI'M6Μ$kS[S$(ա#19=OdD ghii4zu[l֏?G>/Jٱc~v3bW\c;;dh9qfŗEE^t۽S @@2I@e;$UVͨޱfOy,oþo(c#~oKޑqNZ9Jʞ[RΥ{o5˒7Jel;R)QnqO4^~pKWWf߇o􆤑LCG݄Y-]s9Iz<.C\0C@8tcG,dI{iO;2ˬvmֿ޶m ictihP?'gw7! +<_?6mt?z|jKm 47ܨÎ|;XJ) нguW6}U׆=;pvތB@@ VnuI"_v|[NΥr?ڱeio۔ }깪oxg|ĊA J@/f[Wκv_vo6h\QNl@_%]d3Io;jQqɱ 3AI:_-̯$ 3e2TBI6m6IS eogkG _+8@bbw|϶D؀V9[74'X[_ڸKN_΋o;ޱmݷSN3l5U=A:ۦ<=ڴ} [Lmm^<7zn›Q @ byhX<K'lR-!1fJeyO;eQY"{ ".)*ߕq?WȡbOw0mg2]oܹC l쓒BB qTϒ-9_# &J6mdMW)Q\Odg!G@`@ٻ(j hwy 1l7Xp1οЍ|q- AO>Ռ>xy.U_Wvީ7>r!.zqoϭst,7sڔ3̨cGGwPk86]ɦj^vo,FNk̿ۮmmDfyv_V^a>8};FlJKy{Ԇ;omX)7tm]v.o-_gF8@:HMR,S1/pμ'D{%f]Ȩt> Ӵhu~\[0olyWfkR\qg^0^G}\g7ħ$Ef#vR<);gVIhsGI򂏧K#~/n#%}0;}?IA:VW6O2G @Xe0eBiwd@ii.ω}θmGdoϸ׶3_ o?59;n/Pgi7&@}iޒ$6u  Jf^ZXp)^TcdC -6V_MC{cKՂş˻MGug׮vBYz yd۴s7O!ϾR^Tǎ~>:N(\t-{{M J>\D:jԘ@Sg9j%#,^PO\_Wk >[m^QǎNڗ%3cxv3ΫOz]eDuKK*,, $]Gi[ ZŅ{6AYXeD,~.2 :!Zm cNoZ+Y@_O'nNg ]ew9+WftD Q%, @?Hnx+u'~8N5`?o8\*h] !7gwEa5tˬmXE#'<#j쵑@Vgen;ڨ/ӛ1C%ʘluAjo\7ztx>~ PʛI+H@@ۗ3z<I%;/]Ku4dDK<7#^N=H W.k dޕw9x]w +3dGq/c%8QxJmc|(V}Gn &@+-wY2V{YSGH)=P6PWzPq-ߋ/ b*Kק=]2`j'粫Xv>KW2 ou˺ [99WamѬ;xח;;+(&I$!G@ s7̥7aҶ^͑MioFΔ~6JR*${gH;N/..oVqРnZBnqMÁ€ D7 E@F¶BQQQ¶tow_^O% ^Yg:th7drJNW΁csd́1T`}1WfO>{ߊP 9@OXst-՝v;I\k&-"#8 |b_mOژFQk^X剮亿]{;NWfu9x_I~^\[@ErNDӥ $?wL P/=G2W-`g;-|o˚J͜ GYW %d~FeK: ^smLA vF@]p89rW|# HvMMMmmw@]}uY 4F=퉳>NSp:]F;NbQծbBiM5ws.j;԰3ỳϪ4HoSO0SL^{C$[}ͫD6]<$+0xMW^$xNo[joEk/.mlߴy/}jr2NG+}߉^5SS{i6_S7I$m _$ȿWi9AqIr`Vo6J޾bWJPEk9D oK@ÇlNI~ѣ ܹU1bDAWfȐ!naa+&5Pvӆ{!h gfg}yDρ{Ɔ]+p8gOjqQq&{kAѿ)j;ތѶӛg65vIԧʆ-5CMnWȦ ͤclz!/޷OX.y]s z+ϟ?;;45ƟGsS$7!@Z@fi `?k7/Ҡs=7g}v@y4Æ dKSRR666F?Z~38#r6wg]~ ۷/W_}@vs @6 flzymИG{6;[R>vIvm յhۨ:ћ16{ފ!#+ (YCygOփK/o90M%*wV'9Eů̺χ"ۧ͛pbQKÑ_s\g-+ٟ+$ORy$ʇێ?)W%Yc$}_|I>'K_*$$鯒*6(8]/%>/zhu9Z(t_di} )v6ݻ@@:D @@ d7QF~;2{wޔݻw'tRڕqzfBvi򌺘֏9;v%G"۶mۊٓtV믿^2mڴ#z;F=V oUF6[3л&>]&e @ ؙuJ p,m4 ~`_/\cov&V5#QgsPm#J͒=&+ thIu..wwem:|^sꚎ?Nݫly~o:[<;E3]w77&geY{ʌ}8Q~VY51rQzyV/ncgmdwTlHI_%xP}_+yod~o_)OۦE;Xwu@2kgMkKFtt]ta @oz{w߰뮻SNoLb۷ }$@g9sfoۘ74rcG@ &%? ˷Ǭ[nHkk[ 3f}çNzk9rdHq|ޱۼysM['Nlty@Y;cxt-՝vd@| |=vQ )bԏ^Um~k[ '3讔#e%@Wankm+?UvRjoJ߶Iw$ $y[{%ߒfK*)~_3l''e?tDm'.יr:Mq‹(D [peK'  pYg5>3?7A&+$ϣ0KΆ۲eKkWsvGI}ml{BU-s0λ/o~3ܳ@ zu{KvN "gmY~%uلMX']& @of 0ҵTw|$A [[OV﹕[f$;?vmWeSqZ<ΝrZ]n3ѻ Oe6Mt J%bf7{JUqhۥ,Kz]ܓ:]#kLɟ(ɶ͒:|UʺZ*Ć@N D͝cP  \pAx衇lN˳FN;-aLt?W %2SlЁ ̷6sYtK:>\*<gyuAƍk ۛ1yEEEno믿VmZf zK[zG?*K{6}ٍC5\>]߇c@r]};~:11#jkkv)og \o! I2ՠ/  m7,IHyqpɶs=&Y6S?mn{Q @. _|A ~+ɛ]:ۼ%YSWKfr]ܮ'}p|[Yu鳥e@@@@ m=Y;Q',H^V|$Qޖxr,޹ g@GG ƌ      @? 'hn      _@_<      $@       ~u~       ~6      5#      O        P       @? 'hn      _@_<      $@       ~u~       ~6      5# f4|d@@@@@K #   ?/4)    PZOZC!묲ymKma }~/z'    g ] <nZ״ֿ]Ck2j @߇j<5CD@@@ =J 9#`ڡy;93$AGzrs[RPoI[o &B@@ gK@@!tK֫omu@M/ߋO|ofet" )}!M @? $˺΍S//Y| #Y3 @ '=/Hgh{kNA!==@vw 9@z/\ @~`%_Z3W6Y7j Z>09?raUFL!U !] 9|mK]Egkۙt6H3Wu}55@ "Zޱ6H?mryK6>#9=@O@'@$C+!'@mKd&agS8Z24 H $i^}}Fﲯ/cEG@9AT9CjȕQuڅ?ҎpKks}A P=D sJ.9J96ϖPeBI8PP#@ƘmX*fݒ&pkW&dS+ jo pwΨLut2R'k\{Տ̯'RoC/ 1|+qn>FKksTQRV+%1YH"@. @@@@@+fQU6~' Ɂ<,00HխYX3W^'68.9m^4]cۊlҖ* >6kE퓑msv'e6/|@==oU n$0Y׷$;a۵JVfq'g.'OK%`F!r}8~Kmw  t{bvW-pTavBsGhOͷRH<,E,;|d@ @@@@@Hnq]VޮTq]\鏖'+F#AlW1[sOdK+mI;1ǖu~j$p:ѪLo/S~_zD^ 5"    V`O\/\,we1G%mR2cmA NrZ'AnY1Z>'׷ۀW_')죔sҷm!U]Kn5ڞcO˒St4UEf;ZW#]=r@,}wCZ@@@@@聀}6]>5cy^,-ylAA2 lh3Vt_[nuݒ敟ițig ^vR6xf.Ev^TY=peSӗ\_:VI_)2ˍrcۯ.T;M9tD6p?mP eNeٜ`Yv߹wklqp@ 0.o_z    @<-?e!%fttXgv\*{ڪFv:r^nyU   'm@_{#3̒t /xry7reL@[ܿgu5_kT*?-t8iLw؟XL KÎ[ي^jXL/;@@@@@ 뜦|SH$@jntEUݡu0ѧz8%w7Z{z_y;Ѧw/A8frI􆵋*>qA-5z$@G\TF@@@@蛀^hvť eRo:ML; ٞ,ZPkߌ{3FK.KSkB\::VY@0x奋]2QmZȮ@;꺣D@@@@@86r@є ,i:ZZŁ޵ jin_ݵ&e@"nQˮQ+,vKR)M0NAk4     @mIINw'Χ\kJӌw8ldZ8vGwdy̯&9q%     38rUkc[{ f-[SknWF.ڑxߒgV==GJ_r,"c1     Vh̆]/ϞaƲz ivUV7lZlRKҗHY^zgdY+%}`F]@@@@@ژ,14Ͳ$XVs]ˮkJ"0Wėq}`F]@@@@@og?$K*~u*J̺Ee_QJVf~'ɮsvTʹxQw~QMe5;}jVI٪u9睋@ ߇h6@@@@@TR!^0 2v/jST@YuV-2E@(2srW#]ㅯ'whiJr I DC]N(@( >8ӎYse6ꛮ/~N݉G׾/X:۶~uZ J'}NTg`{jq=B*? !*#©Δk"w;J;Quvݶy1XvW{ڶ%?^4yy'@IDATU6!@2p[AAvZT= @ G]@@#JgSԯ:`x=_8@tjyTq'OZ;[ۺ93;5!_'4@ p7O~ 69y#@@EPWjg PIdB7J;s AU"[G7n1aKkD)#2"@;s®9of @h@@92qU˥9/"it=U{/j[3a P, r#r( @_wոCNyP4R{f͂~.XJ]J"@ />ǘYop7Qڃ @4|!@H!P=yڰ}\?j'\Vmgۺkw6@ L}xq;R @t!@('04 @@`G]+ABG!R 6(2njktKͫ:j^U}ICUmtt~U[Uզjұk  @: @Xp'ƊXJŶo3k뗷˯;1 3?٭;w}/ϼb\Io4TyĶoh}ߌmzÝ7_ۯqi؊T/8p'FĖݿ5'k |nix0GzI'|,9n%˻.1iI/"s7]mܰcك/ρ@(~>Gᅥ~⿓8:U1?Rrֽ}N @  ,B $+wp d‘5 7Qʮe{UbJû/? äop5cAz|wتO_JC:2[xtZ_:HzC_jqAށt:~hM0 w9JM@I 勧pQA P̾arCG-ѮXgvt<=ocKol{dum/O6o9inzn۹&T\{Sk-#Oŝ۽vZ|c*U]5ᰡ)mرQV}@:k贩~ts}Swmn~zcܜCB~qpN ;B*m̲[6w)G?;܆Ac͔qÛƺ]5kG׉._߯~}֨jmkㆹ'~S82~lew.}nS7W덧kLh'5cXTuԀ۱쯛#?qѭSuC ~gͯ9{ Ms3dy @ ?ˏ!@@{>\r5)F:h3+=NsuGvu{1in_nX%,M92C{ſsvwc^Yq'^.T5zwZwŶj:yfwY _3}N 9!@(eJY{ rj~IQ׊wv~5ݰݱٚ̓EFMM:v낹kdۖ (tmvG$sҫbYwF{{I @wp#T<礻RH @ MkCBLz:7jjE2ݒjd;9- w./Ώm[r5tL ևoYkw쎹oONu/Bkyq>MI ~jBc͝O ;dsCYT0'_gYַD$8L wV1b;[hK;:Wװml}FoH]'H0{<؀cJ8C`8Fk4R5鏙oĄ_ W νh1 =Cʟ}o[;ۺfO]owv\,9uZ+:s[._A)^Z jɑ1umxcgL 'Gvs>'-g!QCֵszR䈑%TI',#ɫ4=BqWH7.w*? ,J@I  PRֶ?-:eTt̠[}ȻG[U+ɸqѽN=|@]sr }NN)&`E=b2YY&2JW+eI.Y'TrQ"|iF$юS"P!@F euN ,3KF4Ϸ~~udlӦ@C}|vrLb+ ʇ@}'e9r}NVdB ؖ=e3XggKb6o2O)C4f%2٧^p%F8wi%B 00-+S u6Ti W dV E0SlssǞ|u ap+@@n ;d(ϱjUq@yl;N[?#+.^7]81eHå'5=&◑Nc9]q%c+!e1T@@mY۹)iy0nNJDLøF'Z0ONU\_5A١/A '?~99b;!c2}N@JY)MW[pQ\"yMQۧL % nۭ\p}򈍆S/ݯzlco\oo就j5FU8cDǷ{'?(?{sϼԊh]s̡W=.{GNĪ cv=uǦW_J]d|cW?=8a@r${w1}Πӿ_￸}cvUGNJl9*b"l֊ŖE#K_Xs >M:xH!CE2O^wl3 9@U"/$$#[߻v"'E1jNw_+m䢗}aV#"M"-KxȟDN}\ ތ~^.z;۠e.Nj4(C )rB'%9XD*DM-PW4t @ ]K#:UMQs{F;tvm"CFטHurՐQOZ\KN*cc-sGKmyWZv^K(8I *bouX+2cE:b-[;;^\߿L ~4B} 1z۬o  @mCzI_ 4^p䟣Z#OĦHU(yW!Y5V1r>iim>d~t?K4WHǮx.A5n!ijJpDT8w'/yƓ>-+D}~#vKFFOdGvk+Lۮ|v]8CLJJmˉl"_e:):.;J>sRfi*#td >Z6隧5,ed_`.׹x3Mc9)/D8}0}P$QPpF\sVH>\ *@q_L+7cqӻ\2q m׈ޗ(9ל2@yȅ")^ISd4y:V\.tL+ Ym@! 7`; ; T,J@{tmgtvWuf?j'Rlvź̞m/>i6?hwGkkrv,f;ܒi@KLfF~OhS'eF{o>kFc-T{K-tq*uwJ=OWHԎL.qi}?7㷽e>S;|3oGfs :%_t痬P\^䚝$ %]@Xuj,3b?η[uǞ7Wگ}J@@;ޱ-DlkTXǞqOa-fGm&=sw^eH&oXn?cpZN{|gM{;cg oǢvom֍ͫ;t|@rlc!yv"Nw!EcEDI2M"߹"iY2J |"A ?YJ7niaUA o(U "LDY-Hq"^^o rvHH* @2 Ȏ:Vu%ŃLmzV|6jG_?aҎ-[o蔝^q,y3^kĪ:vGG{qf6VnhuέS۶mwaKȆ}Lhul|+ڲ=6U/dܱ~A%2j̤Z1X/m]۶CsCG`Ѽ3YϘ=m?~|!ScM^$"T$ C$E/]=_uQI](ESHSgyTkF`5rr=#Ꮚ4`;e^"jsw%6mE"#&5~SJdQzu\0`Y}>QaS .jU1* W+JƣM9@un)߇QjF{mlzv޷j*'?cȧN܀9@Nolss3|@"P]]}|?$ٿm?n>O]_|`v)syyue5X?d3$8ho$n])uGmf(rG!jm}MD @ @ >5`9Idۮ.\59oȏֻfM; W F ӤQx|Q$ѭK>/*#$p?E8-IF dpKD/); sH紡!ӋSG}ݡӱMvuP }d'WEF:J ʉ$|8wć@H*buG$<<.pt@(*_ @@>T ,jc-?/߼&ۄ_3B몭FrnwD2aw% g>gyj6xTZ&;Z2Ř?K<^$M$U/}~Fb].u.A\#:NJ g/{'㉫NWuDtG"H_n@'x: @Y5&|隥(Y @pSRb?jjJ%jGDl+ug?/Mɂ'y08-wh<;!/C&g_zzzݎ7 u%A\#xr )uD;$jB4?H"zAThN,.04 @(.H,bWG;z@f5lR@>g/ B&}NF@.Rtg'r(W"rT- _n}]I a&*;n;%GC[d}|^һ-o;j,*Ng:~v`52=h  QC;PdϧPRtQA PêU2՝XΡe8ETkTf`VL&}NЄiA6ʔ~A>LDKA_/)9 ^>.ո&HP1)>ߖf3L{E/N_zYtW:}SP`5g.޳MMQoEE~)G`[D w2xR8 2"`ko7@9^$ua p>'at9s P,ƏIZ&4kd'K5w )R퉧QP7|ы4QiN$=ۻT;tW K82A\#6\HPtHiA"EuYԁ9 @bwݲc锗661~Ъ[C-jć~<ןVi*9Ҝ'97F]0jdY&1X;ra7'@p"e00=W _D1x g1mHR׋{HD?;~:m׈VN瘋ȕ"3Eԝ-%+DD.EqH'A3ZsQO PKo|ӝu66auջ.9cN]v~*a5+ƐBgG]#:$Nݪ, }N~jc7{@JmsFH(Ibq|}FeD͘59*˶7ږ:f'XJaΗnC׳Fۗ$%POOt5esH^nW" s\1ݡÉEL˶fj?Q:Z_vDh 0*-d]*aC1s5_kZs,޳鳛,Bgs'?mX뤹鳧Ps.ca9\OkyUo ϲOh[ ȮD!w ,->i u"?]zm"^7W&z/In^$|JI׸"Es}!A\#eN5$|H+ g'$|կs^WvhGD8~'xC] ./tPw$Dø< tMb.?|9wNQ >N渡ڻӿ @Sw< YH] s.C7ibu\';%m$r|_{?5 ;M9D%%ƴw&2Y>'9_*)D[dODf┗zzbֶ́t|"DD戸o%N/2ϐ8Sx(A L"ә)3"z ;P""to&"zdm7D49J#HA{3@ @ @@X6tnU=~8#M+HR,$6v|2<""""ENwy "LK_,ٖ7W#3E/FDtEV&dnH"aӋ )=x`lF6!FD"8N t;orkH@D7.K]Tw(;.rL-s{ ϕDf$n@b83E\(G"fҋNb":wږ7?F9rs ΕB~qpO$u_Q,u' 'Z $E>/is` @ @ A`ѼyVnѼ擲^~/ִLDC}{V9ټKv&VuN'?'VJlQwQD)\-Kؗ˙4XB?Td&}?鎸\]R/Ra"讹䢗 7K*})pM`| i`5|KQ("zͽ"A6d*rTEvݽ0;B @ @@XoTVsejQ)s h~Wz]d? FlFhSwΩ,] DiF!@ @ @ 0eC& @ @ @!. @ @ @ 0eC& @ @ @!. @ @ @ 0eC& @ @ @!. @ @ @ 0eC& @ @ @!. @ @ @ 0eC& @ @ @!. @ @ @ 0eC& @ @ @!. @ @ @  Ϙi"`VpG;6cmrig̩`z̬yC )h=̞nkrܽ-#R0ގX_D˽˦Ľ˘w e/Y_ ڬ{}kܻRX@9+cx(kjUGhǢgWEwVEj"ƲjzL2 vW4Qcvw&v] -`m)BX_ EY_ EXK߹2\'a 62>u udF_x aJx& PC]WӴvj,󋟜sM\B2F0e}) gz ֗0%\X_¥f/tC4^@ ̕>u u! @|Ꮷ?1~L] (BN@J;wvE^3-ᱶh@X[+ͲǖKKpzb} --֗SG9W  @ ?szzU~}P//#VCFc/<ʗ KTDcё3f7=oh;p%at'sXRy`}O/ Rza3 _J>JLJu="p@Xsຑ&ba#/r 9, m!jj0"`m:,i<GXRy`})=2 @@`5@`lcS]U#m\kZBjTX[֗4X_,X_P P`L%A{ϘT頒0@XsH5ꆵl#ڒ?K֗By`}_/3$RzeV `@cuUyAn茵͔t9`m *kKZg}! +֗57B(W/Y@LC]f6@ uJe@ P)>}kKa}ɟ!-'֗?CZ(O/Wf@HC]F4d@ ("Kl-f I!PX_*[ @@ `:@A (ENg%BE~bmɏ˗kKe}ɟ!-'֗?CZ(O/Wf@LC]f6@ 6܁pQT:֖J?#[Z@`}+C ( u\(8,uGN-f& `}) v:@E`}53I@a!0TMqźڜQϘ5Ʋ-ӾO9y%˴"LQc]"EN!4Иv pqG"J eW$~4.uw Rl(861n?-ҕPJ0(@ @ @ȇٓ,[6D$8L wV1b;[hKI$\5_152iSrI=3s$ދ `Wdw{Õ u%0[l |,Rp;odT-+%PX_+K i< ^Ufhrmv3[sT uMp1ҹ(8\ȧ|ϒNwTJESLC](iJE~8Y[G%ڒnY_gH I%ϐʓKyY즫r+\AxW"|;}mFi02]@ @ @@9 5548 ~:K߰Eǝ=aLumH wGbK[Bf5 XQeA1۬bmx;ǶIa$%F^<$^$CE<:ݱf_%BB0k/:yRdtGQ 9H"ڦJ.zGif5)T?//!1q"_p7>rTGA 9\rH2"/'D~9}/pXD32D82@o-~=@!&7Qڃ چ'b%!M_]MU \ꆆ$ TתncӴ?4)RU}eY乛DԌQ1dOMKm%ՠY2ըฯH@MSV/Ư$ 'yHNOEs'z/IRg<!["W81w;o'?([IX[6K i< W֗By`})O2m92M$ueˎ@js.oURxGgny7K ?[NƥF@#$_"7\/%2;fTcHS#rΪ?ZD׈ݱWNGQ@<&I:w*e W(9ל2@/'SD|l&7R2~-FǫDE 07U:G V:a_T9֖Q| [֗By`}_/3$RzeV# 5bhRzz'n[F'?m֔}dZ^!"}g7e"ƺi5ϕi0{yH/K Yt>Lv嫝Aw7WzA;<$/4չw_wIE1Ee.:IIDwN˒T^pJX ?YJ7niaUA o(qr}D9u"Edr2TI?&|5]M@IDATNjԌI*$ @ @ @ ,b3-_ƶk>?Iho#bt5j\#?0q5w8l&zo+ujPrgx[q&y"nDpg/2]qs$'H9N6_L1X_|"/R"q}czCmoqWi]TRp3Eq=;`f q,c\rH"ͮ45NDY]4҇rzfWHKD㸳%;$PW${:@L O-v`m_/3$^Y_gH I<ʬʙ@uu bm?n>7jIX:OȼI:aY u5֏$<;Ypo@0F6W8(ӸE.D+?F[^ҺZIɹV󤩱"DKAǐLS"^ĿIG/豖"Y)NԲ@|v.?A]#%vq δJ@iKDȪx"K2Sݍ@DyG׉PW+F?X[H+@o/C@#Y±/ȷ~msHn$KϚv~AO/hnDT Ig\H"n3"qGH\{ErqH!7x *%݁N%DF:~a Y"IH ^$)twW699c`5\bHY)_%8i;Iso\(RH|G,K"@ @ @ 2#Pmew]@%db]h{]78=,͞4oT kx3u_BdB '7p-gG&ڒ~Y_gH I%ϐʓKyY1J,uG&N.b[;}Y(ky4~5}u~DҽGc5)zox KJlFpxSqP))rc`C]P$X[I[닛a@O/~Ҥ-@%D@wla̺9+JK6սmK—[&ryqWkX곉 N4[d y /씨{vܑ ҝd/OIzqj N:!W m8%'G*oɡ5.h @ @ #fa9{HU/CNNߔ _HnV1ANR֛5&҈X"^"CA'%:> qxǙ6Ô{%rqnR.*Î:Hr"sKNE)"r`m8;j7֖?CZ(O/%PX_S̪l Xv^gi;ifetQct2yWEGN.Z&7ы/.SNޥ ?Ve: oU&345dL.ri72rD!In?i `mqHC~`}(A> #xOX;ҍwm;;y+p%7˘'=Q¹`u[nUXZ ̡2ioԋO)e$ry?m׈s_I00&zkX9 uCMGvH% 5˛kKe}ɟ!-'֗?CZ(O/WfՋ@LRU$,{:FlzCkNQ\ɠȱ9w@InmeSb6)XzqJ~:- QL瘋Q" En$;ڥ齆s} 8n1jԩ|62G%g#0PlWϐʓ+*Wbq|}Fe\ok3fM>ǎXWYѶ1<̼W|5¸;!Cx'5O<[~O<%]+kI/RD6yR$ za$K5pe3(6kk{]YOI$W F$?: |;&wa u#AE4omA (_W{Ɯn̬g=Pt-7F$貾Evʇ_,"tO;u})LdI@0lc-KiٲΞ>ki|4%=5"Z:؈eO_VeǮbE=rd'* (.~Ds%\&,Qפp5&U/CE8Qm{؝& eQo_w;"?y%vM D$&L(GQ[G;ZJ#&ZZ,s\q"EpqW{jI^x͉'Iem) ڒY_FHJ%o͖½Kު(REEMyK{V4}vݖe]eY[?w{ӆN۟>{>2eJף7W՜z";9QC#KJ|yzѩF 2jQW+sݥ+ {=$~HR/U="i"^#]Ӌ$)zW}esA Y/$a5Ҝ~6q" '|ӝ;~I׾P(^@iЇ~Y|nG}]T\s-r&kK(>$&J*dm)-֖#NKIoa'g<1ź[k?cwJW,;;]rwIZ'%VL{5^Nr\Do]6;/%"qv y;.r&s{ΕDf$n@b83E\(2M$^u?׹Զq18Ή+Kp򛭟׈3{$p}";Mes3k?E-(qݭ"e*Uq}GZ@lkJ Mt6>SgqZdm%-A H( ֗ چX_|bsO i\R.G[.,lk>)[;JbM~qZH>IJ#{V9ټH\ : N B4E/#z(EK}$@ X_\w G֖Qg@ n ЯzvM} , /?kvoqԠqQ9R_3ܘkA.kK\B2/}3D`}Ga[_wGp/\}jCAuŠN cC72b9hbgm)=1B`mɗ4K_X_-kK>[X[[KuOFUWaը3cKB /-y2 K8dAysQ^2 @ n@ @ @ |`ˇu!@ @ @ 0@}9@pT@街a=em u b`m)K|u Pla]_x.*A @`<3GR  M CDi @peC @ @ vԕB F:R-C@Q 3qUYt#qXUbe(*0~8*!@ AC]!(l)lM%at(e/=psQF@ʕr, /ˊA`mɝ%!`}/JC`}ɝ%!@ʖ+[21@ @ @ 0`G]ʎ`4?u.;0B;kKiQkKY_gH I%q}q? ϖ ;֗YQ P.0ԕ&Gi?Wb(!-%, #Rb c(!/%, @ $ .mC @ @ @ veC2"~.*kKeC H/AҥmT6֗? @!+% vGzO-v`m_/3$^C\Zi/>@ @@I˒R @ @ @(+M2 o8PhbW1/,}ѼfMf|9jd?4.b13q9g>+kK= kEPy/Y_ ϼ{d],q}(kg @GC]@Fϕ@ww 7pF;,S[[ϳSH$f揜ikkKbkf8%ڒK9'aYǡKhTQ a]-"rl;+JB /{K|"GK[lI @-`@ ֗4PH|!ga @HPDA Oa{yqLXʟ?)Owҽ Ӧ3FB'碁gP @KC^ <0>u\z_߰~CJx_J9M?yb՝$N| "!֖QL%3r q}h@:h@ب@0%Q@,qr'۸aSJ}F.%F`z.JrF%ϐʓKz ^WϬ\}" P~0ԕNQ pz1VݝRja)q"(<֖3GT ֗J4@ =l/OZ !gMH̦Mme33& u`Qd!gѰ/ ڂea"}nMloo7X 4(j_iΝ̋/hlj> s *Ssjugh!ZEׁU+W+kȑ#1b@Y'8o02d9C ޿]cX+f53n49>;B֖K_ޥOu~6D82洭 )?yZօH*~*Yg]ZЙ_Q~ayla|N< ֗<R=f̚McYci_tOf3cV2cwwX%KLWXsTE:rfdKYNc.P?SZ@Vic'3B3ƿFc5倃KOT 'xG`xx|1f%f|w`>ԬZ::s^Ԥ)-/6s<'_TwvvСCzi·|)7h~痙WĈ?p;5r!g.O35].[X o̭?Exoٓ\sG{K3#9lݲ5^7/ϖ=r9^nQfK̰aC{;?FЌ\8[GD/+?KSʟwݖFy6*M1jT:Wj!ENU}A̙zYky ud/.3$Eqj+GC{N=sf40ԥB /^znLnϞ=)Y/,{ѴN/uWlbZbߚ_;}8~GG_n&. 6Y\@N|gg~?SOTt@ @mI#vvz[*m6"kV:CFY a?e>rE"'_}U'JHY @aoN13kxo>&uEDž3?iˆi ;}K_7'5'̉ǟd-u1%zOyFV#SCs?byQ' ?X_¬Џmݺ]Ha'q'!şٕu+<z _xZDt./y^""_MZDƊ $$ ~eƯ8ѿ>xYfMtw>ϚZQL p?H/k9̨Q#hW͝e~waw14O%:|̏pSO| {7\o?peyG͵|l~{s~'|!祽.Y9~qy7y_@|sJkmsC9IY}qN=|K;5O/~Sղ|ZsYBX_|Yql^2Iwb6mzC~P_WOq}1&Qߌ(@GC{~*r$;ݛΓ\eV8 u~0}@ 7nj6_W?{gUusg&#kTUA+ j[VjբkUE+}K ,Yf{37fL~LJ{=3y9[X;nu$:>ᔓη3|0%:l0uݯt V: @r }w[Z:ft:Irp:rȑjȱJckO#o?:ܬ׹'k?(^P~]@f? 'v{>H{뿪KJ-`iw+t򊂆>kVzFz'uKLK? ߕ|g$ݶRyǿ']+?\DЪU?~`+]e}eI2m}"]ڮ/iі\FvF @ ڕ'm/>;A)$'u~ @j- !T5?W:w HG~yU8~n?y2 %3qW_tlz䠞r.o4!S JQp\8OQ>pd' 'xU,ҫwO5 j /˼{0KKMsY^=?yȿ˶9}L/c-([gj>K:dC9zg<U{(Z-|ԣkwגٽbS܌'.q9CaT/TbM?hRWזFcL#0oL ڵq Oeyw^SZF)H ;m^Îa^X ٕPu"?EҾ 8~a vÑ_]~DzeJvJ !ǣxm#'yL\f+`q+5> [ܘ}0pY#G L0eš}25*NR>_pժנv'Th OwNЮPU':P0ଐ@TfY6`NH9Ԩ6mV1I 0>I,ז̝G R.~wwFX'hȚ1itGu]nxȯS!0(v6zE,NB9 'd~/|ʑmPq(waJGР 8!HK &S&irGN $u%㤳tJJ,对jպuÃVV#:V՛R^RW\suݯO 9JTY6phժj׮]puX ,z9S+)W-q?p정>s3l^Չ&Ma6yyŹ KY?{ p}wnK9 p?6~dvYSSJq4YK v=x v-|җ֖aߘgㅼ+8a5ų+t7,]7+.nۍV'eVYq*γ6Z|%(ƍ8+ː6Ɨ7C#/ǖ.zq"NrO2;G}"rCW>FI&⠓[+{9\'Yx%` Fi$$yM6MR4 ?sh#˙6%J pmi"TooR;wD6bUtw֊p7*r.%E`>}{ۓڒKb8f]v|?q}I }/ؒ{F-7W ۑ←V-ٲZ)AeɬՏR3<6#;$ OB{}(nxpY7[YUKa: t7q^y)EG.UGNø!y=B9_COZ 7ndG-”y_P*b"p3Tg|'[(zWB PHREIsc;vg+ߎSNvz ̩QMז%`--9jQnj;rrrh!P}g\JFه/pL쀳.ԓ5YO0`/j@D ]}^NͧϙS-77˗}rgKD{joՆmkԦԒO?TϾ|xSj~u8O>a Gp4A=-F{q-t֋)Nؘ smi`חp+.Ku*hօs_S ZlKK<{EEQFHy0'eRXUS(lం%͌צ;P+};d/T㆑1j/vaZ,QKA Kg%^\Bݶzv;lҞ8 $uI[$yr8F9k_GEXy CU'uq(U׮UHOnfk433UE<t:DM]zmw-oۮ}yǓrռ#eU rI"qč0/!4=^֧PO/2Tv I,-.Z\si% ˱;*&O54$x)UCdGL6)1 $L5M㐀A{g6-)x4n{0nXjต 9("=XNˆ&~y^eO`IDҤ-T3+8.UۇvnO}+lߏJe>cC@0F|r?+ z '~L:^ǶoV|FⅬ ƒb&Nw - 9<+_6L_+ڶmYצm[מ85h  9x7gQM8K,u@#/'{ڳg9nb?H;?r}w˯,-[չgN a(NkWD`b(ח$M\o9s{jt@*" f/4/jjLZ_x_ԔY Xіo\FM3! ȗʹ :4T7i@-[~8_;&l^Yևiȟqex/t0iDr@ykxj;[kI| `0ոQNvo:T7 (1`/ K_!$:${剅t|VRZp}u]S,4:5uyV9.9:fuzڰ*Z;+*4*L-R^w?%j埩1ƙ]]СG{t١ܺerJugk׬Qv1?z^Ǚe#WpKc9.-\[,rajs|MioV\DC0|9go>84Z!OҜ'{OfO8Ӝ|d=rgMEOHyq]լ/ wAwye纵n ϟt^}q?kPOp}I-d_K19ټ׿?Q9r]*/xJ6lx,fҲdNݹt:!F!ф 3Z(4wކS.@EPMFeʱ 3 7m3邏dTm]t(ێ%SEޅХXCFeG8nO~r(6wkmJ4xJ!&1hH)|OhJDԹ U[nU=]׭[_ǫw]QSh?d<pmI}uڵk'UaʜW\_XpaÆozTktwl޼,ܽ4\7ѣkx-;Vq I9;EC5ЩS'milGsS HK''NPG}t˗θkmܹs-uoXW:@:חv_$w]]A];/MV9dQ6|8H@[w)vNE>UklvQ{D}=Wn=髖WN? jK@\|)qЈ*=?2kR}8n =7skVp, 8$,oEoI  /X>m\rIO1ostILUVIuYN>| ~,t{sqjjjYQ{ꫯyQ#)nm{キ#??Q5~>}ze֭#.%Kl? i$"fͪӥKܹswL0afHH &7  @SCnAQ 7`|uyxIŽᙋq3B=*v<չUNOQ bیq` PQ0}D0b|~`ę%H:Rǚ- d S]p$4+ZPP/gOA̎4ұcG󩧞z׶>]v6p\&vy睻pnݺm^q xGJKKX̟?ٳ+_yJw3lٲe۱KeӼy{M69?F L,k~d7_}{򨣎~X}үNw~q8]ve1'x~9S-^z/⽋/^RRB'$bwտV7d:uof+t=d]"YX6$@%{g$ ph1\WEWaD,F >D(ѵri|Ҷ4=-Xo hv$sG]џϗ0nw ((:Cg@o@LK[0 43pfѪGvk׮ݲapyVі+r)풆0[ 6@IDAT+Vp^ډ=A~#8…9GwqVg?'c^_| ?{dξ:xo҇7:޽{{KYH;w9cdw_([L'H8L<}xN$[}w}rW ņpY+I#p !gv% 8-ʚ3B[OԵH xozls׌\^vKv9]9TxҊ±UdRjy#H]8r|d)_]v{C.,PNߜAM_Xh/7N5䮏gc`RC#}PO}Km|AvҿE8r<$Wg}z>ѸVMPe2LZ#|"tN:9Q$jtԥ8#  z_jkk o鐝k 5k8%3ٍ(HHHHHH +׻q9/WP2(ӬS5d^gyr2uBᡤ*)p2U7hmg;N)hHo4L[P7\[g+)uߌR48MCɽYOi"eqi_3Kp]5T{nޙwiKb\Sr|ʳvIs-@G-ܸ AD_PKAVvB%. @K cUnʕ9xꌷ~z 7袋| d=_^ѦP@rG gC.Ʈ5Fm2YN?lY%V?w߽08 \#gއ#8K *p6}gըIEKTW#Oc|wY{?Ȏpb=n<9 бVJ޳.;Lq#OI8nc%HD8E#2cW^FB}Pqux't64$ގ UN 4uMgǚ$@$@$@$@׮]ͷzkǭھ}GZ#FIW=iҤpG$@$@$@$@$@F݉1άP԰)%+8>]6ڞ*tM9΀p:pe8 =jzk{vwai6>.- iwna8ÎVx'KI%&&(7B]4Q9RAPqIݵmPKβQ^MwE+QR_aY7zTv͕Tb7.y$ht%( @߾}=O=TՃ>gm|UVp)ׯ[=jԨ:Q$     @]j¦̺Wphq*s8|jzq%BLNX dMP'i%cNĸŖ82A6ogBgGptG$@$@$@ СCꫯcCD)$@$@$@$@$@$@ Prr uP7l5+C™S~:9A$@̀Kt]$          eeb T0/6丠c\#ŏ1G]8$@$ pG]s5HHHHHHHHH9eTvM800RRjxlHHHHHHHHHHHHO>N$@$@$@$@$@$@$@$@$@$@$@$>tԥ=[&           hkϡ ucϖIHHHHHHHHHHHZ0:Zs$ν[Z$@ }/*$f^fØ  d+{QcL4InIоͧS/3at!tо '  l#@G](Cq(enYsf ճזL+חp"%?CZ>\[oN9"  !@G]{B#P qS=HL $Pkevdwi%%N\_LK˵%xd s|$@$@$@G]:m3BSU^-vN] |?ޙf-p?f/3$%y?^\[wn92  @G]f{A'`i҉w= n\Q~Gʹu-tdڒ@\_ח"זl!%[f  dteo$BާGq4Ʒϵ.T/n:qGuYtkK&v2E$4חMKkKXR'!G@$@$<]TcKH gljŭVwk4 w{rs\Zfev X{wϟ3ڢ`ڒڼp}I|e{/-FGkKtPjl] 4o}Ѱ뺁  pK*e>):חt9cnNmlazvVNq}-lY={7VoKh8oތxǵ%\ pmIp}Ikf2am=Hj暭 %#> Ћ?AoJB$@$м LC#t5IfI i{yb2Gi^Fh2vѝƸZtkKL"D\[.j\_FłYGK2kK2vfڒv#Y  LC:ue hQ7\ ߵEh잝=m>oqg 9 5{*.u3ɵ%uR pmI |/iFSMKt-ITdOC'" eHH"0 GHH iczՔkYHHHHH"{2 4Gi*W @'0 C89           h @vЏa`$@ rmuL2H QK d#0!q}3b) H11ihr\es$3X5|nUtP!˜V 0 cAe$@$@$@$@$@$@$ {8Vk4+.\7­q~ [^h NOi5u7)se Ti$a-s(:O$p Z JID y*v_{Oq(g\OAEV_)^RFC_VARS'-TgДtB)'84: :J!fA[E_BOYt48fd|A      Ei8^U.c :W).pFS<0Sڴs@:mrle]μ#+A9w C>P:/|s E27zof A;(ΌA1˭VOOU KYaSCdx+F)Asn@'C[CC'Ȑ~ UMp^JXʊIp %ⰻeZ8nAKn^vOHGyCs.=&[”|n).?Vem$/CBφb!h$LcѨR x > B{ntw֭(۵/;,K QO@2NԒXʊ 'C)ΨRބ&)aJĹ}%0h8g7赡 hȜ:KK`+8,2B{0%m!ed,үP2̋?QnKX Iy襲Ms?ܸˊJ$@$x:cE{w;?LKg]Y" @f@F==\Xo%g t;IB$PʿlD| Ix:t ڗPqȎ!Kd&_Dje8bE@v$d Tw+A2 ewjjwN?=+'u;²k/D[VFEZ= *5/qЊr.ҿեO} XsB 6ّϐnP{:1uc+].Y.ou T>KiD$"\hW=;WC{i⤗Y&A/}>[":RAmx | $͚}uDC$@$T[mcݲw~ҥ/ @}H!9wTР @Nȃ~?a T>( 0M7 L1N;&N]ih7 ΀.J{Pq\ NLnu$E\/"k,emUEqXu?%⬛wYI?O *NZB%Ce vun&jD^_p*+sr^ aPqjY"ǚ΀i%;5ZI/]DlbDǠ S.gNy5}*<_yC ,BP}2CGB-tVB럐'|e&:_[Ϡ }y.[cYo̫tBI'?̦tBB$@% 鯯>8?ϷnnM5  9ʥeq=yG- <)hsÔ{(>9biܴdjkC;bjf/Aqfh0=A;aQ%,("qnFZ^﵌?#;꼿k⨲;${9T=_H'Sqǔ]b)o|+MYS;[K=aNDdLAe*ɾ\B0GO@*s!*q&Jdy sҞ.!rtT"ákO@uyˠz"F(<"Jq~hKnT^zp%%l"ɼ:iI@#0M7=B  4gݽVn5=5 ]`$@$@$j3A9ۗIjlHʽ?,l.P}aFKeqgS7&⢱H oԴ&ʾ|#ACLqXv$=MqTjha P"a΁3M6Bu^enF\9){0l\ۅOn-Mf8,ex % P1wCc:;"ˎoA:ÖIHH i0?VS=IH TXߺ4I   ΆLgйp}ybJzN %a hr~_ KӠ`rewPqV`E>GX$CPYwE|m(tq =k+3H۝Sw!/˪vz*뾣_cgUqP7Dڡ eWBXb:B}ovZ\wJrʾc:l? @qbMbMUZ" h*-;k? jHHH*}v7.$@$]xGvgsP+AG6'3{~ƗΖ%wܹT8CJv ՝_&LgIDSvew*GFvgnG?Q;TuYGb-,*;nʎGPME#r,LA-lpyB&0Nlw%4 @d=>_{9|tB$@$@HzdrL$@͎uWs]n-òc'4' (ʥ!E8peR'1I컙kA%Nu($|OUy ::ZWZ8͈ %іlbJ*]Vh-,ۡeƐϕTed.z$Lܖgwvggyayͤe @Y3 @z"eGS>T2v[Cǂ7!7sBz[ 7nȟ 8Hۋ` c.bVVBn8U)޶iMEiOw*]aYpm&$n4B$@$@$@$@$@$@i$0Ni6M$@m=%3HM"TngZ' uDIqF#mmuc o~f+o39"\h+61ՓqnhD43e߽whQAsRf͚hNn4XHHHHHHHHHH {CqDĽkP/?Їv08#E))&%mŭл˧Rg=lg3'Eٶ2h;T|YK׃$*eo3wM(KqFsRQw ۡ:WͫI)Ɇu'FQi0\/AQ9z$Lx-;[C7ʿ0֝9?͇KDL]hEJG],E$@$@$@$@$@$@$@$@$@N-Ꮰʮ3׿ACBqCTUZ8BH3>m D 2pZ\OUk?z^ք]Y3HHHHHHHHHHYX+Wq > q퇊ie#&AZl[#,KQH%V Wq *RKA@|m Etj #,;,Z;vb=1D6c"         P&\=S՛'.Xkfˋ^m=8l,GUeP92\wd.~Pn,#1|-,KY ݬ'",~B_,CTh('جjH=ޖ#x|tH@ҥgCl9Tq(E9w%D *uZQ"$         $ɬ_ݹguOi@MsrG/]olFb*?ܐf6aKTG`K?l]xب^WD_4> I"6LRV'nkCt8t4XC>T!ci<Qy.( 9_q k+2An V5BG@0__/RF[}z#%t#Eq{YH8 t=?@e+|i#H8_h(;'FBEo4׵ە=V'!ivtB DK76AٟAqN쥂0 uu/{ej٫g,=\dzF卉LEΊ8-r*.PYqKY.8hTvT}(CRP 9NЁP%N&)t#4qj3 Ν/l(3r㥾ʸģq BrPgt-DiG`NQ@XBо5PqwSFr8j`!t+Tlg'.X77 zҷ u)GIHHHHHHHHHB0\=D)M' N q|D#'hie=Xʆ0,FѷR#GEB)&p2ܖ)D|N}DىUvHHHHHHHHHHH >J 'S]# g.Gv=#           G[XňDzChrZ &u<9 @# N:9 toq]-΃nʻd3Ny'0te؄;$@$@$@$@$@$@$@$@$@$@$@-% j9)>%D!sqxeM ;D$@$@$@$@$@$@$@$@$@$@$ lONhd? zQ4Y&spG]{B$@$@$@$@$@$@$@$@$@$@F@vh}׽,ϗXrh  Z #0B_ΆVB)͌ul]          H!S N`E)YHcHHHHHHHHHHHHH QS d#:qV9&            '@G]O;H$@$@$@$@$@$@$@$@$@$@$@$YHHHHHHHHHHHH2u?E @6.gc"           xte$@$@$@$@$@$@$@$@$@$@$@$@HH  rLiHH ]Wꍥ[ԚM{~ttx*޺ŵtvIHHHHHHHH K $`fe˃B$@YE`۔(,g|CMazYo(cڛ7#=a$@$@$9`{B$8|X0q,iHH Q}sA@n=J0Lÿk\5(8* @fp: ӬxT]zyGsEŮ^Ҿ'O>1uTOlHHA :$H%>,JlHHTY_S9QHH q:Z:dݝmܶ߹|eUsomv~ou.¶IHRE 4!H7>,`$@$@LL-#ZZoy!6HH8Kki]Z;z@׶g־ Bp$@$@-lA͡@ '[' K6$rI=N3) @ }ww-ŃؓZ=!  Ū<(#yG" #}X÷xt}Xg $uIG iq']3/vH o <04cjC"  HJ$@i"͒ 4"@G]#$Lhrd܃ HH ],=Nû K$@$@I& IH |X0J,C$@$lt%0g<0H'ș,;H$@-@ֻ [p9D  I yI2IaHHM8ۘ1\؞xB$@)'pxq~Q $L2`' &ÂM*$@$@ #@G]P 'a. LuI$@$`"+ u)ͦHHHHHHHHHHHHH" @6c)ݩ]{ں3O(ݳ)QiHee7GK$@$@$@$@$@$@$@$@$@-)]0v(sBmCah˞atD: $@1.&\,L$@$@$@$@$@$@$@$@$@$柬ڶoa^;_Hee?GO$@$@$@$@$@$@$@$@$@YE sPWIU`Hu9 @|Ijϴt$pIh)k)3q M`ʿmHHGA$@$@$@$@$@$@$@$@$@$@$@$@$@'ugIHHHHHHHHHjW~t q(Tj34?s7Jee.ha2\?evL5P4{ iýҝURN.8Eazi.(8SLS;f)] Z7OMPb:Թr4=ʣVO7{{T(ýɽsR\IuQikqߢ__P:Aݑo@xgH EKh6C$@$@$@$@$@$@$@$@$@B ;0Ǖ25 n5D?QmԮ/(՘*i(r8G K ۏQGv lʩڸ /_zۛi:n1%zѫbP;ջtB{\N :Ὀs}uꞋ߿1W>4KH>v]I~0%p> eHp\WǪ/0Ty T@"i|#jqIRDG_4!         N@v-#Cѣ;邎,? ]PhFW8>'8hݝE\83~p~̈́ d8qd(:J N=2 QIwM-ٻacѤ#MqM9ֶs(U%C5ff-ܲŽGHǕisE }tC_C0 <+$ӠCчVxGStN% E~$@ u H$@$@$@$@$@$@$@$@$@$MmNw hFओyۆpw=+.'y_' c>'ǕV~VOwҷ c2=xF9帙;;=h,OK'iN:?~Emwǩz}_x"=5G2L"H:RM @s&pEfNe_:TZizod~xOWx q_}_u 8_4MϽzvuqʝӔ@9M'C9]~XBp Qέ]T!Iv+ZZӸSHRAG_2 H M7]?Dtjt씖~$і0dm       8<Ôͬ^];]X0n˭rV~5 UT=݊כ9VW0[iѸpp;udUR8yø?Ž%U%W]sf:D\ӡ0LDя7vHb"c۶F΢HBtQ|ヅ:pҮ8ѶFEE1ݺu ={ԛ([2۷m5ޘ_߮}djKrIGkkjYa{mh4Ȼ)AtG۳*,ͯW|aaq{t,Ac" I ٻ⃂O$9Ρ?@5>H ;N)a;~O ya?"YL4iJ9~ta_~C̸+B8fr`* a ?ZrfH kk2[E>{F͑@֭cn3Xq2gwts NwV5rvYe瞙䤳umEc6W/,֭I'r=͋s.NpN:UUO97nܠ'e7FHHHHH x.h/A{Yz#iRB{D|ulNpVa;A9/Δf1P }yv<` .sf?ذ~4o :{q禍܃$n6G͝L~8,ϛ4N<്鄎 B 1vH .%Grbr&K9PC v`0&`^gh>J]iWO3WfW-S3.3]c[zzϜ=bD pNӜ*(U ϓ &J V[#yK%h6 #ǝ"@N:4Gh1^U}Czb:#5㜘F& '~ uk-[E3'L:\K7F5׫]vX`577Eh&bOٓ1NW--rǟp7~Yqxo{6.fk֦衲 }đBm]ֿFdoUv&MQau&{kQGonG2} {v(r܊CeϮ^TT׳"mU9kiS}f* ??sQev |!SD\j읕Jk]P?*c]]L 5cê=u ضw?Ի1;'j;wl"@zԥǑRzXiN0lE`p\yN)e}1:6R;{^ ſ9??t yp// ޴Nuu%=f\W_{}jTVvY }k*zS\%hcF 6>xK.sΎf'cšU16GY\~5Ng4IG(ILu}ho--5zjH 2UnqKz|  y*PcŬyfIՉt̒tFAgi:٠{SFuE`Vtcw[A+g;<Ǯ{u0˫V^VHߦe 3pHv k}|qU ͓-_J GW)kM|wkw>sK}gM"uC=o#j .N>Wީm=L !&veW:G}LLpmEEEZ;+_[[kʖ@]05!(r}23g)GMogMNC͟%ұەk>M _vELk֧~~7}{.\k2U.lw lےbޣA J: +MHG':: Q0i9I@WG+"?pȷʈ'V9&=#Ye!uܘqIƺڜ-Mma%9{.t;;>xfڭhK5 LpC %ݿfIF ,1-4,/1njɂtnKJJ7:Q:oy[6*1=3Γ5iΟrZbc(.)AlcsɧRq?|O9̘QޑnL:V0ƾAc @N !5Y0`: tc|^ $(?v,((a: y@: O]Pvtg06]ҡob\LGA]g\|=E`O>2sQ+]jCJ]t^3|жu|s:f'ݴbjzvA!7 @?䆺o,^3ϙ/1GLihN ɧh99W[];\j1#2e'הrM,%~_WOV3eycs @ nZ>{%EKTL_--҆S`ڻ~LwG uW)9q;8q#mro3a.h|g(QeTJpwrMs,8?a'TSڳ!ȏ-['\k.?d?#~?aD ti)*Gw<1=L%b# L=#u/M 4u:l8%SF_fNz @Y?"r}}]]7jqa?׺hL_z]cC4---V{{  "T "tf$gt9Ry@\;nr٬O}z#] wAu\kn#R?4MGΗyТ,hOyUnBa 57IvVӢkS[HmZuJjzھ`YɓCJ}'YZ YNco2pS]\Z{ay#;[$@Yk'Z:p<(KkEkm:+?s@` +0'Enu`Z卍zvd\[ ި|eߐ \S_oz_FUUr]2}T\c}6 wTUQrB=i;]_P}S{ݽDr[|oEgsy#7i7d".@+[yF8.ݰ{֎BóJ_LuT[2r$,K˖eR&eo[,?,^5BьR|"u,zuCo>(_WviTyCF(Ⴙ~*.?0\羽 衘mk*VlYu8u!r zO􌋌,MuRϠhE-~F{gF///WcFIXZGFRV3 _G+N[@TYNYwIYH':: &:}*7?kU΂H_v B@ K 2=[%psemҰ,jN,_M޵*d&T,;3O.,,4;dR\춰Z![ʌ%=sh/"K*((HXӜ6znTٖ)嵱w ^*++Kz{6c=6uT=YGfn}f@,8G 8Ntt`~kic8A@ .\u%6iX jVN#CVtc|Fdh sX]%*.ki EuUz ™pX577w3J0][[8~axH=E\=|Mi 4F=N;#Tp;SF*@@.YYy9mPT{S([>옿({d^Ig S4 @ ˿ה!F2_a۷&Lg0mǎu1cD۷o0:C.--u#t0r!`u*׭#k@@+ (}Eg[ ilno$Pg:E: [Q^F@:4. Y+ ~URRxk^z&~K5jT48'S\ꢢ_}Քv*mɗh{L7|3fT^_';rݏ  dQ[Qлl{m}zlg܁ԅk@@W@+J@ 8pQ2z.f:]v`o822L yǷ3b.;vlL555wN>`Ŷmz|ahc*ՏS[ZTTܛ<  dB Ntt]>S2Un,  \@]r @ 9fT ֭ yϹw^C$C{;Dx6l?qtRy>]2k֬wΝ;{ZZZ?eڵk7o#X86T;si4m^lٲ*c=-e*iCO 52ϣ;Zz贔˗WM:-|ظjhPsIgz˺כg=7}?]|sǶ5!3 ַ~hgi3Mm[ejiۋ/X^k|ѧrJI:;찐L477|7n,0}ݴiMr7L  dRDg: 7Gg=o=LGAkQk9$ڼyͶ$'竼魣tph:.\ĉ6嚎O?t+Rbb?zm4d1"-QPlj'O3ۇ|Q.ܻwM1*{@@DEErASOmygܺ/ 9˯ iӦ 6E4_7_[\\YK}ܹIF{l@|&(hh16eYblY~%,BT oA@@,0Ntu:ujaWUTsSt`ߺ Y @. ^8쳛GhqSSS4'5 |fC]vYrm St)ny9x2%L 7p .5H^c?Nj6sǽk3}L*u8p֙)1-i[PPf% ۗ m`3eNqL3>o޼yUTT$͛rՍ  St+))I{: |}MG3g6|wdݎ ;%icrgJ~oT: \F@R`D]RC0Q !̅ݻw|fě򼸎d0gyfI2mڹ{_]{JUeYq]YYiHU}1vˉ_k uL#iڵAy|gF^yqf(yy15eJL߿T}6    Bo: >Cf4\|Qt|嗋dJep(g?uݎfV͛7xw{msNSonGA7HiwS?C'n-ls,gv[Ξ?ߔeٷT n\GK~"I"߻su5F*>OtHzW;U y!@./^F    nY7#-pCN#>XSwuS/@&2VuR@RҙVڑHԙ ]@]sMRdK'q\ge D2/`)eom/w     qmzw[+^eŒfd:I&[ 4I,#r 䟀qH-E #YH~v@UEID^i;m%z\ā|;2ե]dLsY#pIH.SdfJ Y#kri9ʐV6^8"[<6 @@@~ Y`Ȯ-=S5J:<.]:[>o~N |Zj}!dtW$}K0,Ă@N o本82| \ @v Q0_j#S΂YkUOUC 7Lb/O5Lw[3|s\Ofn zmS}2c`@>~|[Yg&}{ɀ @ tuD1}J6*#y%@gx9e4]fWmdG Nklt3#vz"a3Q[͐/ yeyʯ} ݊b@u-z\$@Q(g̓@ g,8|/ݩW11tՠȭB%$}Ow$uHJ%}.ɸc$-tJ6? gMrJ@]N\T6Αu׵GFtv&eޔsfH|7{S@^YeE;B_}lfūB%rY΂Il|EfVIսhіnc}i_6 Rx%J9 leV|b9fȌ<{]IfytGX*)Qn7>ZIbaF.-qLl%&Ɍ4%EI[$ i˧1ߕz>zP/r[bs6ph9(}e7lZ5JF Α@rW C(X@ MtL yaƹg_7mp(2%}qcs\~ 2巼eJE>Š&YNȪy\Oz_ 2/.`@ȅA:WI]gBǺZ}D2s\5owLfHrdwVֳdmfLyM&I't+ hxYFE]t>zP t-t%2fbAuZڝ/U5## Ai@"PB-M, Mi 9o'A3edS/M*2uzHV)2$_9gYE}]d??fT[Av6yqfT;$SW;hg;nl&g{s?U7ʾ%]$DZٸ\y6]U3e=]ןŌ.Xw4iB^M@]"@@@` QpĽ)5x<)(>ܯ8+Ʉ /p)H돹%V :CpU0_Q?목G |f+l fwK2πK}RRd4bLgQsf/H<#ϣ9aInDپR}rF:˒tnͲ?]yce?6X.d#䔀S     S)2:{bފپb{]ֽ-w cۺ4~k1n<-fw1y$6Ÿ[0qwMo4W/%QK%w[v{mrQuQg@@@@@,j{LG "S6J]$~BO{{mk2"1HK/]ʮav< nN ~     QS$RU Krzp>+H2 HlL)]{wloKr=|ѵOk.8 j)1I{MnÂ@ W#    0a.M1peKKM~2؀@ݎWg5VJ& Js }˔Y36ϐ\H Z}FRHG9@ #vPo@@@@@ X:DZJ' ĹO 1EΥ0tt<P/zFI3v9"v7ޏy #i$bF{@3R y)@./_V     HqweOgϟrޥ-kժoz*YAY7>񘻓d=;q ydI׳;Os>r6ω5 . f9v$eHu>j5A@@@@1ZYf Ų.1ʅn;.R}zFœ9x_\17~6o@}Jߝ.n;xgl=ʶbFy<;H>\I%K@@@@@/ʽZ/BX3tMeYt_[T˯['ȖâgaSZP[RQbÓsE6V;6$lDK4ͳӄH.?R϶(>u k߬HH.Â@ 0e´@@@@@@/eVg@RGoȺυZ{O; A˦u+v{lKg$I˺?!7%iI%XuRY>+^.$,osLPЌ`)doJ^RE~YYw %i1 %MN>=ѵ6ꯟ Wx$     `X}YM,ԉ,2bRY]%3WBKrH2f6: -,p&fZɵj%@],oHIs4^)MI%H_eoKܱmoQJ$yN)ɮ 6,#FެɚZQ@@@@@,M?vuVL]L`Nve] HjsSǥo>)zAf7̈ܢR&%rVIq'LpΌЋҙqlsMrŒItu$w5#U@H}i7     n5d{]P9 9`=׵ﭜVl|@ 2Ǖݕo/.xֺ?oFEF]yS^,$FF$yݖS1˯ds{E64C)[z]&de_t% cݝA{k r2+ԗt@@@@@^^X^NGO|ӵѝ/,43ᄇ^z`FsrS9ADZn [&;Ծw}L2<%e4UqH-Li_&s1H3)]K&LW(`ˬ}i      @> 0._]چ@ j냧'J>No.N:y!-jCʴF!      bpnHxڱKf\\h_r%fvzSk,Y-  @vrLO-x>Xt/oJOC_ ޜ;" @˝׊"s˽%LV> ~.;@q :CJ0p ;N5t߿hMoꞚF@rQ _:}:_v{BWRn.vlr5  9udF6QQxp:]*:&np{.#h(v}b{ֳؙ$C.^iڃ FmC>[T(mY_uuGSv ̽zr=@HtjRDddJS^o8mR 6H@q.@æm7iFrmV 䥀^# lL;e1ұ,_R]-v۸Zu7nK7Wr;  ;P  s1t^3)`vSG4>s[4uq&G  @>tul:؝uDٺ\#^|m{ݼ̼ߤH|m/B@R P@ ed|҄bFҥFt/VXcʲɬY@@ gəHc՘(03~(#:z {.9"6gtqdA`HkTdyV]ޜ! 9$tvr˃f4͢    @ @Wȳ8( m{UuE  @ ._\g0C@@ !@BDZFfA`8~:pO@@ `ͯȩ[׳~9-  @n @@@n&ӈh4:T@uC-@@@@@@ P/> V(%VՄ@6j#    #J?ZKc@<(s g4Qg)u]ycMy\    #e 0gVq,7餒nB@@FfW:! ' 0B.qcT)e[57/ږt7]ڜ2Π]Av@@@`8 c87  ׅZ!@ U;aSGcF}itK])9~ D@@A 0Ơ@KFJ@` p`[Hk?E@@`̄1XAG@q  Z  Y/΄a*j 6+)LQZEfD@D@ݐ0sR`?"S uK}8}5E0,S.ϕ|~U|Ɂɧ@Y*8Jx>n1*M8\k Y9{ .+Y Nž|z(u X)8J ت. {$S<_OQ;5@lK7m[D6k@@@` 0{i.  Ȃ9"`٪/L,<ʲDvhiZMo&:_Ecn>^/XW8q3?62P\3I'9v6, ms_81cmWq "66bѻuUo}SG\1o~4Zo?x; 9]d3 >{]1[[:Z-mo<\  3a6@@pԹ@ QJ|±1eWM**wǵXf 8{6,͹<(PE/63Dē`@6uP@uߛt o][޷EWgۥ#SPZ]t'oXӠvu$LSyj\dK:+: wv{cRoWE4fYKǖדNS). v1h t'_`|nfvCǦD %XZ,?Tȏ$e3^'@@D 3<3;F%fJP  #S ]3<(3aTM86fwM_30F  @ _U!=Lr]gt_3'ZM˿Y5 NhQ_~um+[- Xß'{Ccx\Qt?j6}!j럪ʤ#>]NqGz˗bg9wt4?m6  03.ϓ '"q۴<,•onօ{U=NaheKu$}cUIG_㭷ɐVy֪3/Rgc[w}ϭMVkQJ^6Gf܄BpVW%}g& @:gxH䔯3aԾj*OͺC #;c  @ g_ͨ 2I^xR 5Œ=ͭhews@릖Rg4V uWJ@VVPm!],eI(v It]ֶ'ZZ}]nMFUKc6_JGwcG}[!ۖ_^~-:y}]n'%͆w+uCep1# ju_T'L=/2>hZ+/Jy?>,#'<w #@`!i▦R~W[:׎dPyŔ2o9 dBUSj [t)NQ0x6' q?%|A!K"Y;i%JѲm8jP/=R'3at IuEjn5kS[|G2֟42{@Ը"rA~pPCH i9Ea~]<+ ES+nN|ǽ>иStZ5wwZZs1+ gev NEuA>6E~4\S.}]n'j#i#&Z\xcm5,3U<3Mr}ݼ6}ܽw}C2Zcl# 0PY W-0Ъgu|S ԵLW|Ҡnq}!b[RJh=7[d]S=Gu6}8'.՞zl}]nL3aM-umesn33aiP~Ri}t8UjIɈ @TIZGa(K@prA Kvդ?{~osñd)-oN]vOסeY,@psO@"K9Dw۔9NP-VkŽiLffh[7|;izF#>e,qv՚#<,eH-3,2jOu$!Dk9іdDN*Ԫ.jY?;0f¢IGGG%rܙ0Mlo{$WBޏoȜkQn#?Y@ &÷Hnep! Лt8Y/޿9&P<͏h G{#twj+X)3VosC1Qr&  F@]fX<2S@f0'>3.;h};Qܫ%t@LY+;0h@h$ԍ`.",[cF.[H_פɽz!"#Lcf/,*DPwkT-fc /Yoso}:}d4 ZƮ:Ǵ9QI'鳤$gvnw]1>2KUR,qF7׆457鄢j:|)T";umBT{[wd2p6@@F@gxH-3a@ ZVg8#E`x`)2/ ՚喝&Lb 痗.au7לR|.SNþvo$g]\R>L%N%nt"s+o,1@@F@/]s% c_rrgˆi3ap  #J @:>R>{aoqѠQFWJmM!~_UPKGvP μ6~S+*)2i0ο76F3dh#mN@PQ^&I66X#\m4fI  #!SMf&LR. ʲS,d[iO(kí֫Tf[&YK^5۫-|KY-ˡk~…+ՙ|E}};{\[zWcrg-l [-N6,mu󵥛䩣w{o]dg\(+;|/׭0'^-[MsLmyduV~1SRgbۮk@A 4.¦,6[UP-IYn<͛u Tolu9 E!Z7=<pFRkG@SJ4f& ¦C̄?ܲ/?9|7ejh&ƥ_جׁc=OJ̄x|Yh-k&3a@ [V-ܶo"%dC YU+y=w'm[[:eraL[k}b}O_ q1-lPO=3d¼MG}%Rn$yiJ r`׵JZmբOZZ{ϲ[As]ξ-1~yX# 6-;f.;eO)bynJ;i5{sq^|TȌ`"州u3s?[ZE.{l} _ڡ߲̿ȏ  y-Lp27`&ԘɅC'pޢ eTe̙1>H7w>_)O8OF=뱖Py+,7y){ܿ7ҋSM 'Kc=O{͈EyvRNT}c:X@`% -(6c@@@&۱Wp]Vqh}4_Dtkm-Iu`WU?~̀砶tgwЛ2Z/>$9%#N^lnd Vr¡>ҿ=g{! Ȉ؎ZPY*Ƀ@&y?f|D@r_0r5 @. XӽܢmK)imKǔ- E7n7 zmuw>+%xiE*ϡ_zʃX֓2qK?>权?W#@*RQ",p=55/=L>^68ؐdD }'ԫ")@@2 L@HrE6N)߲V_{N^uB_z~[;丹;A 8[W_.>#0rw%e@YjV+[/nSr `(#ixwokY 0[O0Cy_ 䒀;;A.՝揀sߏ2Z} XZWTKPiJoVxOo3}у nei=uw-gQ{QSmw[Y_%'@`ǥ Xv%r93]Չsq ȴ]t߁@@yY7 ?ZE+rI0rբd<.nGu i{!}vKo~˖x)\\,}cq~ d\} /\_}G&Tva5Lٗ*`䛑t]A:e-@@" ?-5̄qNupHi7wy~";jD-@`(ޗp=s7\z}֝vn[&YP1Mw Xyg-XV(l#0HurH.`%fdc7'jt8eo\ H70LǧԟL#m0d63at]A:eCvsnY%X3e]"[BUtŠ/+e}ZF~juϡ\>nJ/؟ueiKk'q:Ԯc+,Y>o׽C@]:) t=b얘LZښkY!_cJF V@@` tM[?EuKȺuZL `Tڝ`5A @N h*.y|pBj_e~(mߐe-K:JJˌ%Ns檠zQS]B\*ZT欥uOŗ% !*>hy^ZfpәM}Upc*Y$#Z,ԏʴ׺jbW3V=UU@t捬-n~ MC @@]!   0:-6/0MNfǪgT1,,B^C?|oʨH̲Tꅕcw[A+g;<Ǯ{uђ˫V^VjBV5fHFZdPqU !4my"UR8bOY}I滽]ۿY_;zp$?Luܩt6@`>? ,`4ϔQp9\@/[tX_ʏ UlW-ҪDTӵOE+C(`Dk jՏ6[/ː .vlnwv@\`ԯu;AXՏJ kL'`օ"W+gWmTglᝀ>ůWd 3I`$,ռKFʿ]%?QL/Z }515ޘ}io?8|}~GFO6ϖo_w>[9Ci#O//LQk8Ol URPQV#|&<_]|&t& `x`~@wMkԋT!ef i 3,>[2K+gKv|"gtR]X:ӵ0x>  P75I@L"ǎt`RF~ N\ijِϖ!a&(gK.j9+lS3];fūA%@) V6CHugA |& 7wzDu*o K%P0$&rTe~U% a %7aPP$7Dq#.,bpTDgtƕuGGFes"*;ʒaG9]{ϛ:[yOݷ>+s!u6x\{jC Cr9F);S&80B@ 6E@9u#gH #"nDBR  ;F @ x_ɢ֙3&TgC  @@p5JM$0x@])t"-#ݝKҹefq|W'cN'{md1`u@VmB6ԁ #!!@ ȦC"JgAPE @ Q 07@^? PFEؖqCF`[2h >ƂF!Z؄ԪC DQHdeP dE̳.5.e(r `- +]O +؄_pxjG @.u.  @$d@  V? @M&@i1pc D#mƉZ PQY## Y[7*ڄ|@EG]-:L#CbL%7tG:lqwtnYx_/@VW ]<^Y R&uM]?~ɨ%K&4xQcSrbbRI&. yW3^À܂mq`C  SA`T@&t i)3'>k7_=ok?}lyX<><ό<K!{+nrG^5modvsYx]XUIYjpP| 7QJl>\V%?s(1&[K<"/EF×t9ś~Q6PfÜroQ~0@ @ 10@VMhےȪMHZ7uf"p(rjk̜tq[sO{^Q#%{[1HLT¹xGWҦ.#v`hήI`Jni9-7T +Up䛒uZuE?9FidWG]TN\!8Z2 90 @ SeCfRYP2gYU*jK#|ۥf;l]`=s# |I`TΉMu)PR'1ϯ wzF6t @ ~#Ko:NL(&Y}U`bZP4zY W\Ou=bKN_76VѠ,#S5Koeܲ`Ngλ-%vDI\ zbӜQ"e1T@ @˷W@ d<6!k@VmB{hk4y??yks?qk_]GE{IN'qCx9U:k9Ous^E(9GmQL${^!el p? PNE9FSؖFQ?+-Y4@A6!^؄ꎑC#ƕ7*9s.&9|Cqt'ڶߟ;o.W$Ak9|TG#-QuiTǎG]ǪA-mO:+R&&7Ch3G"£7NO+^fU.d K(K6j mۼoGdXY '. bx @@; yʷSTY}]`"W d&; ~V[-Z3akVxky<|ݽ7S->Ir佧{{_fSŗ:(Qs ᡼ђ_xck-EJׅm~tr^96Ѝe{]X&Q2A ŧIlWɵ;~_K("HTјwKGUM2"{*B7+q5%%%x\PQtެ;Jv7҇%m[Q6tl<8uJyؖ謨-ؖl6rx4&t. K3 d&4!m5@o_-L &ٖ^h5jŏtw_T~{{qiijFl=)}Jnz^]9o\SgNwMsCVFS +m'%z* ᡼-%%x(dO73PH 'nIs۱,9*?؂O7rE\({ƌ88fݏ_>ap04i~CWsT^}R[0ǝ;IJ_!* \bGn6>a'9m>'YUlȎ1/qt9kG$%(nOxIEwAYV$hYrp&ЍJNFkƊ`_c5'l$ Ls^.1FK/1^{QHLGnX_ s%jktK.8ZNKZQdZ1}d@aK>۬Ix g\7Ϯ -ն/i6cl&vj7&mu&t*f.$UL^z7ݞ_0ݛ͢Wxȼ@_*~ߨ[,U\1'̘uIN<(Yqs(Y79W]WC%g+PR?,yFPYy\A'1-ӋzdP/gZVӋJY  j}ыn:Qo5 \`GIGm0{9\rĜl'yLbs!qcp_l*9)~c⨋1"GyڴqRCԪ8u!O8y} -ASKӫHT-6/QQS]3k#Nd&kkFz~s\_+5y擄]~q~ՙ[zf%b)GMqnxM V)lcC;"jN*;MITEq{u$Jx*;|gΕjAz)yg$a']p3%)E%a(l^x1槙#ٯ`t]򂤍?HZQ8_g.AsO( ${HΩRXnCquLU fUi*lbуtݠo׿N1FXG@g}iiDz g/|mt.r ǧk]m)ҥMgE-}gRv %vjn&vaiNmlB3mulۄ!n/ =w Yܧo1suºUCRSqjB_ 兓XvQwXpSj}eJXonJNzQ}۽vd^WkUߜJ# tesv~f8I8ƱFl`кyIWΞc~< pv's)IdBO({G]?mN^[v;۰oj!#Eowߊ}o4Zzi]s׷6n\Wr nvcvmQgmdAKP$PS{(rb$ѝ؄M@FmBGLڮ _6]*$ry|g_8B͖g07Fsu;DϪ<7MTߠZrx9Eu2xb!UvZ~Nsc/8؆ڌ5chQJE^ ϽQֈ9 ͏-.64\y?q􅋢nxnwLPD`7^z+߬}eұ.^)Le8m_3cI?~^ evɖNmlBK@6mB*UACW$=c;kP-UmK}/wr7KuDsB7Q#D,x %'i֏HaU#'Ҹ#̉nFܜk68?k IQ|A+UO/_/{/U_IAc}\VR+kf~GBlqcY;t9;S>I`aS! uydOb]f<{A`5淟d~7z;'ή-i- Վc>h_Lb"sĉ~[zl#`v8v@~NI8vMh/zo;lBUfLNh6,KD'ݼBH)ũbBSoU͒yX筺~tvU_ *Io7ޛ9 ?tI7gWį0& /&T$&Rqdf;ľ2&V&k.ɶ86!SOÐ4H#=!{_.S,4扡t6CZIs&!ܖ[́X+E-_Ighqm Bߧ8ƱF̱c]U:_rd~?c?Ir{%NQoOI>?IDAT 8D3A i2o"IɎNoխ-,MLQ%28>Ku1de_ŎѴ@m;[#EW C$lBC8؄Cf3z=s?GuN"9#TPVP9Zw֩PՍ;Bb.+i^>I'W2͌w465vS9E(2Kr~]bKs cO.vt. l,p l4f N?JG=iƉ}$N4-#Bq&Pt&&4a4ryϿl  Mh{~x]֖{Bt(/NLJ2JJ^*]Oۄ(%p3*2˪^Ԏ9u|Α4C/vlO$QxHvI#Mς3b8ֈ;|nPٯ=Be:wChEG]+(Ge~*YU+@z$̮v%H! %`&dv }$%'I36!yv:$1% G6ahd]WP~[l3'+Vz+ mX P;18GMJaCeZɎ[cUI:zJʯfN^^TfN:G/ +Eّ9.qx^qs>Egw_c/8ƱFk?;sn^g_]L?ʯHuoLSm텪5/y,i  @@SE&S¥ʐ[D 6Ep@`E~Nג67jYwܞoQr9c:"R]Ey{>W\?8 uuw7_%HFL;oW7|Z7#Bc&»UdMPJɗ$Q骴N/K֒$ezQ2I%(;rxjwxoKh@R] H9jaBqc|Yx͙4؜Ai$7IFK`3m%fŸwi;m9Z[A @H{\>2Hؓ8a6!N @ )3'|T1v}ԙ^{a 1egt(m_]mIܧ~+hde&Qp23#:ߑ|ީJ^;pۥeN^7'ץ%/H zѵL/J&i^NV;&e?Y'}LwINZ4qs~Er3/غX2Vsd\9lΑo="1]oW]*sn>[b2 Y0j'M&Jꎚ @ $ҶK:8@U㝥Ȇ@h c9@@uwY݁hKر`996ub'˖>l)٢9Nmvu;Cb 7s+lεzBpwqL/J_dsb\R[$[K6'rN^4qi"uk; TrҽWI0_řs=ӭŃ`kx`v[pԵ 5U&AkQ @Rl;1d @ T AA^p_29,]Z1wU+w֦KK6JI?>g~((So13vFjHo*+RTy{R)p8Hrs^%֗ێWQYZ%6ΪzQÒKHjzqubXV =*شZaxɞ}9(٪l\#TT[w%!I82L 8ݖ[FyZ8e @H%jR9 6ai@+6gQ}΂U9 Q-/+}M=^\.-],ȭY_.^(&[;M x{B%QveEɓ%K ME[$$<# 툋Jz Iv(]s E/8oVCe ;ӝg86k1]%%%JؚvZ. Jb8Cɫ7\ז-@ QQ%qLf@H;:Co;.mE&c@`Zn tۯyyM0{ |(N@kviՋ";I0`iFsd^ Xh4gGۚZ~B´fN!BR `_& @ @@.]#lB <ޜ_2cnժU+v۟o~}>7#KyD 0RؖLؗ肑@ tNH CbÑ#—ٛ zlBꖙANG]5KCv k]82y 7.{ի =wofo}Pt}饗Jq"h eXž 7AM'7O23 rrfTD 1my؄#C@B82d+ 7IV2茾~Ᲊ|j'w~7~wg?UHOWY]KlvCȀ $Ѯcz'%g#e6!Zc "gf6&v4YL=M粴~ -n)UC`dh[lFؗa2,l4@3̑] <Ҝ&Y{ 9pu.S7$>K1#_>\M&lZ'>=Be\&ѶkT }YǢ1`3(F6 IzHNfMh8&A6!@hFQxld-onٲe7XM@`[]b_!d@# LoogGX@G&tz @ 5pԥFU:$?KOؒ*p WX xcƌqkǭ: ?;nmr$3#Kd oS /# mRp];0[rOKm_G+o:޻ỽcǖGyGڿ{^o'˚5j.;y./})+'9-%%cN#!&7A Pl(x<ۭV-,],Y⭷zEO.*K޲˚⨻˼/}Loʕ+{ 7{~mn@ `_ڬ+ U*#SpE6!&*A 8Z.H!I<ר vf~cAg;b)'}ܻs"׻޹4?M<)}TL$Æ}x/0Q h1ěp|VDN ]ZШXə؄.6̰Mh.HZ ,QE'dⶎ.ii+ü}[zrJ?ڧ}S^Oc%mw'y_{Oo7߽K.Ի+Jc]vLO/Q)H $ѶUK}ƉZ G*GD-d6!+f tQ.}1Z$&7x酱1aG_7z薌uڵ9g.C.8ƍW{^^Sf}Ovo _+ a"iJsrbҢJ&TB @&k?#r-#>zJfկe\=GK__?o8=8#yS]o? ^Tn[WHMqQuٚu~KzZ~γ/9"gvYiٮ#OgrXc /nv)21\@@F.#N4 FBwToM6. '>S;n=qz`tH!l4/mVC ! NiוPԌprM<6at 6!A`( +  {Q4yRY $  (+VBG*Mc)/6!%:LlP&@ ~8گ`aϭɄZhOEec]yܲpGYSO=Y[]]]emKG>f^Gזۑ5uDD / [ǗuPSZ+.E$\x~yŶ+jpC^RFqTcZ2@`5ue%,}hK20zSa%K%G+}bp̘$ F51uyˏKd5$ !`իWպW[+Wƌzu|w]kbC;Hm1ؗt-N/ؖtAF,|BL>:TN憎0 9&4Ό; 0Lf|daw+(XyX<>jk̜tq[szTrԩHV/2R'5ծp:Q>ǵ H]`k3C 1~ZbN $J ߔ[j˟.ɉ7M#U?;PG( .<꺅zB wY;!lq{Կzc.N{թb_]^;/lKh??oimAYFj%q=-ogܲ`,R9Jر%AWr;$oĂӜQ"euP/q<ޱ]ri2"O/zKu:$=94##T 0AXyK f/JΑQL${^!el S人]?ztw&+V-MBBs?QwU|״NptКMa[&N/2FJ ޾/]FH#[?qM 6!I QM;7,QIgS}?Ť8go(cPsve8R9WBW!N}uDqԥES:NF⬫OFUzr*;iۼo? g~r7vAzW걽챦gtؖ*#M}$ym1t.U)/K}mgl_ZS yYz ؄T*r?=5TxEz)~ZZN7u$[XB_UKToS|;" uJ8u WPg5g͝>og~XKQ-EWEB}85/j>Ӽ/[lYak֬>.?wy't/۷mpv3>訪&$ɶt/Qa(OG?^ \ ,qHݷ˃j~xp9RY<}̰ mz!uP؄R&SIݏjޞŁGS]^j+w_L {YٚSzý9c9?6wyS~åvx}3/uQCy%% Oվ@n0O([PrQpڼl+o3"WbΨ tE^ק$֦ILP=kB*Nخ6k%v_^!h̻%&g =vZ۸ޒ{ <.y(4S);RtS_%QI[Q' A~WJ#I;&漮i]m0cv֟kzGnaǩjUwx>\,Z-XBQnm>=Sۿۛb!{Yksv)_|͹kkѓ ?c=Zfs~nu֓* bSľ4hKcİ-6!Pܩ}X7=ݽ+s D#k|rAtYwҙ. F0R؄2~T"{8M aB}"G[g*?qiijFl=)}Nї~Ut1rL95yw]`ZS!+ jέ!Rn;$}뽪,oD 0-bC{ZJ TzN%߳g[=.Q!\ Ք[pN^G՝SfN:9 .Tn_:pQ9->88?KΓ|U2ǶwZNAs6М4Q9sM|79ϐDы=$SVᙦyF=˹ևlNow qmkɧ9U%WVT̟խlGO%tdP &;vw}4k֬/14=nܸvaM#PKvtL!XN x=]=cv,۽S _>ZGl?ϥ&o;:;c?zIk{{zNnNPQb+`S 61^n95fv0L[$~ ތE.|-}<~j<)d'm{r>3'mpۜUWXouR0o+\ l/ ɜV' \$o c;ؗ͑fW ..2(m˺]Vv͖ (WeN8Y7wn|Dם=Pˊqc|Jh9yaINb\,yRrZ8)Xqū.`N]sj1l$ aoo ۆn8>u>Q7h%\_Wo^dI`ݝmVy+>-0aBn"h@h)U٩8vԞͶi7ҷ>{~v{o}78s7{5׌N|qʔ)|ɬq~6!vMt4Dmi@9=Ʒs5ۼIG[)'6w9 z1c{.Cu|a/O!§O 4/՜?xWeV%qsWHJp"?dD~\N:s|NX]Kr\1]&U(ӋҷKSҋ⶛bI^HWɊarIѸP%Mtӹ/vF׌Սmkd՜nSI8氽\b-|_bz}^X>_*pE9hG]+(:Q<9ݞ vo> GX/ZgyDl~HRZϽC]%oxz{衇ϟ%馛MoZ.=l8H2K @`8:%_~y_x6.]׾Vrjwʕ~r t&K_9yrW*o=`73N)>W2%N epZM^+˺u ~vS(v]!1g.SnF(~6ҳN:65bt}U}(D9Izd%¹*'cwQQ&Q\@ @ $ T C@ `!I.;no]eS85C. ռEa'̍zg8uwp qU7(?|4[\/gνzQr ?h0m; - sZyVwXFv M4x\qQ#TUuթg;[pԵ 5A @@; pr;7G<03Hz"v^[̼m[{ƽI$XDLl'q; aNj[Oe<JWJ>U) 4׋@1n'\ÜE u$aV;VZR-R4:'F`B$"4^S8"@  @ @ ֏8"kWzHorW2'qsi&k IQ|A+UO/_/{/U_IAc}\VR+ScwoR9q88kLyH}) JmԬ:ajv @ @ @K4crpjF󍴱&T9\jV2씊r 9Wh)zQR =*' ;V)0e 897+nNJ!V3ƱFmvU0̑%L]xQڍT'b"5D%@ @ @@dO~0^2{vGwyA:θV<»l[*nTvҕ#s\b㺖$T?%#ʜl\&;whlX#5殇MxCx jYQ2t@ @ @ D ;W{ݕNCV#T ;QR4xTR?b$7DqԙV(Ӌ*&A/[Bc Jn ~Ϊ65v朗9.' aWk^Qֽה)i @ @D@GݭQK^=?"?}Ɂw˝sT G){7J|F(PVNqA8\*wVÎHzGH>!yVI3v=C%Ru=\Jqcuy}&[#좲YNIќ"2Ȩrܲm@ @ @ ʪSgT74wu=+:Z[r zpr;eʬq_&?IVQgz_v^tG>\NYR&ۉf΂9X]i%܌ONީno*1g-$WJdtYV%IJzQ|kE2(}^9W566ߖ\рUlc]9jaBqc|Yx͙4؜GHnv;LցksU~Y @ @ @S7SfN:' cnC/33' W lc.6m_]mIܧJϓ,D2J?Vpf_T;;uwW^ɫsm$i$,Vs$%(nNK%K^k^M vMx8NZ%KthlX#/j_\LdGm$6tǻ5ⶾL(k, U'^B @ @ $pǜ{1'Nऻ{ՇQ-fNS%KBsn ]`;(Bg$Vٜka'J*hI^6撉Ÿ.pbIZmNxhlX#D' v@n%{_T BsЅt/)~ynK8ZN @ @ @V}鼼R< BS*m`Ͳ5AYZ*s__rg풻TmCAϙ?, ;${J3in{vT6JH˕qd\"ea9P&J^<)yP^P TIE/yNgh\騡ݰdɎEU7jXᝇQTo2(+Ufw見$ddW[s̗j <[OonՖ8gL @ @ !_h9!cҪ8К_Zbo8ƱFh882&*A @ @ @p5'A @ @ @ u0Q  @ @ @%e d&4=BH =Xo^{7߰c' PASry?\8yUKڱ/Z&jn5qK8&$|2VȼMh5p a}'7B f]5_dSݠo5ǿmFc&xfk20}A:{K;?1SHؗ$F 'Y?dÖw"S;_2&DZ.TۄRW$J,N t׵8ҭdF>Q>| $vsô ㆂ}82/QQϪ{lun :~J_gXlB,m&2L) 5t @f˺vG]C)%Px>M+ /^S: B-d]UB`_bJI#}IFOvش8 Ôt*`vE,LlB  0[*@7#je:lM]Q f @@ S;K EWI؄JTTۄQ# fku:5 @ 0HڬL`vd5c"b tM-5'%'@@ 𷑜+xŀ"2!@ @  Hg P'Ӫu]|@ @ @hy钖FG *'# @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ Dm޹IENDB`sphinx-external-toc-1.0.1/docs/toc_tree.pptx000066400000000000000000001411101453603135500211010ustar00rootroot00000000000000PK!L[Content_Types].xml (̗n ',nۺnӋvڟJmNT HڼSe[Kpc珥Ζ胲`|24Jef28cY`$hk`+ |f0dmB1O1BnZ_BqfOS.h Vl2),t>?RwMQ]ԁW9DK#p49e0W.Ʒ:T#4y?hi]ߡ(\cVڂjS%PZ()%+oͼe֓x&h!6n6F&ntñʹօ.' #X*|IxAᛐd:xYoH`ke:!2!2CzLa\*y(p၎_lk}_/\ %߹EXm}" /: H6R u.W"NAǗO{}sGH3V2r7O/PK!kхQ _rels/.rels (J1ޝmizY`Hfn2$ooDjc&͐0OScj0\c-;0Y)xb` Gΰk<8'-?6"8W[PK!F% ppt/slides/_rels/slide1.xml.relsN0{a@Eb(\HLHQNڳ̶{{+*aɶx!rdo&'(/XNҮe}1eYr(X-g'4Ka됥Xr-jt))[i+^B|\;ؼՙ`~.YnjK7$b\i<hhS1 `yn(O݌w' -!B7Hd3ON0Za|6 &=2@e%g63pq} #C>t>$]rݬC~o<PK!#U/ppt/slides/slide1.xmlZ[8~_iq%7ӊkUi: l@&3I:V|ue\+YsfgiMY?$5OvrE2 l<3$ 8! 5@ &Hvc$!tBazCn[]ful?X=Gb HپDpH1:.(:"lKlx=xR ~GȉD!pAB8L!Aw ɼfe|-\*5x}VbzM*הYc9mL AD,ز1 FOE&{7 <*k}ztu e=twz?(|繰ΑWLձTzl>|KҮQ쑠eD^VANBvc$'RugAvY۲tCbCݻQ-B,kHiYuuArCbtw+w6кqGȱij>0IMCCLj$V~i{fv.S^Xh0Tomy޻6{wNs^zI*0!6IOgٳس1鸺߰\;2A>CXǻMEfIp\8{/'aO<ŲN,O V3Ѱ2ˊ݅pdY*[^k!NM˪iYu*4-eѴ:.UǨo^pE@%?6HlLH~Sܺгl[KoBKPK!"M ppt/presentation.xmlj0{:┦ǠдJc*KFdc#EݔAWt~t >!+2_MCXˊd1wSHY)g$CG"E3o)7LqvJ5sϓŎX^0mxJ}M`2IW Y{{VE[''PU56<$ޓM,9S ZuU%"gy/ŊS haT1+ \x1?>9u[u7 N?8f(ITO<ĸ"Ҫu5U-nz$QGJ kaGkP0ic=ЩנL_ (r@?o~w;¡5*߳x5oev lnY|O~snӪ+JDgcMN/̮:6p[\&|! $(䅠=;hMOu# {>bD=?b@iG@ M+0T,4 ;LJ{@xIX@$\?ƶӊ*C7*CwMsgwyƟՅ G[t\*xm oD42zS t_ZPK!k j&>ppt/diagrams/data1.xml}ko\Iw0ai̴6 è&)>L>q*x;ot[1#QW[qNDFF?ruywwjq}>?:plZr}Ͼ;Z}zwrr{jqO7˫ \~89[._]Hrqu|Z\\?ÏGW4?}wYFtfE˂RK?V~g7[|uty{>/]].onoޯpzsuroFdNכϫɋo?_vէsAݓ=^|zsy=Y_?;>l 9>S?B݋w?ߜ:ゖ臣O7_Wp wυ,V<>_]\Zpqxp90zm b{pj v8a}0a`'+bLnL/onVEw_w7-5ŋIJ()s,brAYDB1,eXTHbhnl7~%K;TN,yn ?Jy{)NԨdXБy[Jya}K-/wxs|<~6Ι_ݜ{B5G&W(MdFTUG=.LjnK)0) 'c,]D ,Fp #,hX.y rl]|#RxƬ1m08g$1&Z?Ԟe{QҲw\iËeq>2I'PWagR3^YX+d'eS-d pBXKYj7_JfJô"t13!dI!ZWcoCh\L^VbVXYA|L%*[?V|XL5mXԎ3]2TeWς'FTc: orcT"w08x=+23ZlkcېR{#ʚ٤`.,57WI}o.o~/^Bk,ˆ**r- ̧PURQ-5nLJbde-{oDJg+J"p."0L!Xj6mQ^@nDDQ2w*ȚL @-T&mku_֥AB R:QZZ[G7"/\6V,yj);xKE" Ԛi 55eXQ9F#m!kZy%+f7Ab}4qy88$BLkE zsWZ훐2hp59v,ʆ۲ Ŗ`m~ Gm[*Sx 0UtJ9#4rs64ѐ VR#zU} a*qHxL+"$sHuppiU*K>XEPjxo`tXzh, Ήp dt,X]&AD9ЈsPXrcqA߆g4kEJ#F(@|{H:dd77ƛ) IӘUwlQƛtɓr P@[ܔH˱hLF"@j7rw@ 9~UgjKbZd/Ǔ߆T,gP%tY0U7|q-r%(D QYbGj)zXKS+'h2/E/LXJ%Sܷrl /oD\JD?!2h Rb:]-~șeK`(șt+X8 X'&J F;X,^hߚ6fvʹ0uF V/XHY+f]bY4L# 9i0xZ[DVjU7'#cyR+!EX sT٨@m*6,HRmJM(ąkFV7" 6bKa TiUбn/oۛ?cH]\}t~(<38x:HWwkoby{{a̮ҫھ#BOT盛tn]55yPx `SFz?ts{:\I*` OEuա76g57=<>.0=7O`Ƚ\Ew5پ(^CH9[}v k*%rXXteeKE1UX C ^!eaૂ@YܫK M 1hVpd}eǹ؇SyT YHl ʱ ^3DǾ$wA81@.`*̓6^-Q{ƋV]Ʀ֍{27| $2M[%bmب pc)nݢP`;&B^e7KLQ5:N)jיQU" Mxd*l10y ķ:@ Z`"jI 2k?#Z3|ck ؔz mp|9HAFj0IPZ, f~c/Nl-1&M" :G?\\_/>|f! V4 o2KY"~JADJe8+hoTR+ѪAn:kJ>3MڔlM /bHEPER_K ͽ@WLJ!p79Pg |g}Эt ^!CT_#S2G91tip& FM j}M΁ZbܘDO29Cyہ'G,ϑ#x )@Bh̜E ƟȻ[X0o0g@ ꕾbb-H 3-Vj2oXU謆yR<۝G SRKL4YyC4ݢv)ZkVFSXHtv6eҨnG;y@ ۜlҸks; 0YH}6gxh tn#P8!bQtsɳ>vLi/•WC\SnJ˄1BqFН/b(IâMx P3$*P'BbʈSwY/3Ca / #WM!.  j@ZAaoUGHK4& 0vZV2yiQD hf(_`ťSg}Z8 Ie 6+YX1Z;;|-ZYOô&"sKo;g &;W "0io7Uq1ꚒM$PakC.ISbL`چ)\?DM6FtX@i`k fNu[M`jM}z>58q:bP>B[޽< jwIMOcv?C>ygĎ\:&=.Gu4Fwkk]q2!;t-ZX~8D%@gWE:Dt,r=..@ix7I )Pݵ}56A(c9T=֌ˠ:h\5pynLd:V/gjDŽ0!WVBH!0ƴ6Z~/8^_7 [K Jc,.:b4]vg緫;뀄iyv}eGГꔗ蔳tVWaEcFpH+q[142:<;\:j{IMvz<'7_XN$ğ\,kivZGlR4S pI$.,)Dj/8 ϱB+CXTbx4"yWgv}f_ ȜŲdF#œ62y}q:צuvY?sL8Db ܥas}!:);vǜ1TQKj7bܯ)~ }̣Itn@n'*daL$#pڻbZD6J`#3VQl>;^NvRu4J: YN@8gS4J2꬚YoOͮ0juT5 /@ ^jzvgMi)4B0üedms/8n|([#kkڐQ3$]d֊XʢxJW"dʀl κm*FfˤK[(`V_]IKeA֚gg{={Հ "gŨ78fA.ɜAz!M{Ybư&DK-*Lۧ{S6 >q%JcVX$t}O?.[ЗYB-gE-ƤA"_}"7Y5t.KƉD-jЭ Y)O=QCXoߝS RHZCq#DmJ5LS=_O_* 5mQơ`Ր BlaذG@g]. G]dX_fX1< S)t&Ō6O6_pDZ*lQf006y|Z[CߥY8%`?3Q:_&xt7t.}4`4ZF{jRE{]{fR s-PmT, )(mIGG,,t{Q|n5pﭡ:S6#8 1q'jk/)NdLk*0jD5j Syg`xp<Ӆ ԿՒX,{rtBO5|Ъp2$L)q|x),LXOO)0.P[ M )m$tkb)deT²Q4U  s- $"I:esatpv1mOrQt= ]3@]J*3&b9OIҧjk?hioh rI{WuSCiifTt+SX[]׎_ `* :K qaOe_TCX 篸 2IZjZcGD^"aECSoЧ6ٓpEyUi#Y6PO1j]TS 7(&#ŴڧlT13 %I+yW\ |jMut\SAޝyQ bmȮ+gEZi4dQ]fbfvS': wq쁶MMjC yBӖW:&-F,I/ay0AOv*F4549آz4dAɜ 0IꩊN^(5SKB oU* 2 0 |EM)g&칑H2$ۻK)!SɘP}2kn{mþ'R\_Й|CX \f5҅E^FyT5LCR<]Nրw.`(hP$"˾Ou7[%plRk M юLOUf|YSUN7{輫ywY Vϸ/ g3z4 ) >P{T%Z!^SC\r<О۠gQg i( óE>6Zy]*={>F˚::7pYx/Zyu5OaWy ]X6t]aE>f9"Gmp訏.{VCm!95( O0LF b}YR*tۛv͋6L 71n %I:F""m,mUJ5av }:Ne,. 䶉z 2b0N:QVUQzr_+0=ldPPSII8JAK˓\W"n-NY+ |_EFcNQ0u gKfJ0OM{+iH1U .^240~#жJ E?2rxCFbEͳ#CpKD!)e8 @<[Jekk}7@~򮩒5J)3'rju4qa(*Uv+55TM ĨU)SS]WsS8L  Q"z ާ0:b0]DL@Y]e'Z }&~,Bq"I1="Mw̾ۗgolx4$=-P`2`9.oOUĎsH}֊01eRV a ˪awRߟ?+o.>tyyӻvl?9X|X.>Oſ+^;<~kwӸ=>|:ƗY^-Vˮ.uuwwp?<PK!R[t2z!ppt/slideLayouts/slideLayout1.xml̘n6=oV$u Si( $MVmm+!%vvI cQh|uYVv}3{ږ&/ꋙ]UZirf_~u//ۣOҫf,`Q:JGgkY5<[5]*.K?*ܩҢ]7Uɸ6d*_m?ڻN1CRW-d UJ2ú-tx1d-ܪ :ֲ,riNJݪvٞuf\ƙ3>zk7/fzt*}.g6u?'/ ٮ7[el^2ڙpY LO qҫ)MWOI[$%B QI~a??:i$=rVE5}R/}1 訤S@8r'hBG ş ۓ&[uBi]ݮG bk픩l8txnUea꺃~aȡGZIQ7푺M~gtLl|`ZRZ=MF\BءhɪIe+k1,>Xd^(M+Y 8Hb(.\/ Zꔢ&aML=<( 85CQ(P?LPw7烇!(yp$=Θ]]WiwbJ]QPMSxu4JuE|u4oDz؋{`y#=]%ޕ8;al |80Eal ۧg}SS%2)sO"B1!@a0$h3 ۟,XqC:TΞ^8"  ( W,Ïan|8=n>$ra}v]fz-i ݵ|o_\]7;B^ @G30Bi$yD;Goz:杶()(;gp2Bب`]ZNe&mnG*LW}9 ѩO PK!W;T!ppt/slideLayouts/slideLayout2.xmlW뎜6_o10l *mX3iR^}XqqeJ;j&Nǿ0JN:(mn H84h"~⇶bES:%`bPK!S?[!ppt/slideLayouts/slideLayout3.xmlXn6wkV"ETP0e>*ѱPdE8}d;mڹ[Ƣssԑ>+g+d_̅O|Mes5s߼Ns^eMUm#fg?;"i71,k3󵨳IۉF?[ΔW^!:v]yC1jUbZ4j"E)_]?E뎉I0.$ul{*u)eU8MV뉥ȍc OŒs-Ki^n/S&z6[;>sJ檫\\Mڍ̜VN>L||cM xp_t^)qѫ F3C"NT9<HQDY.b .sJ=zǤ1M'# y@) )0Fpӈv,<]mޘDD]iZMumg1izԔ25톇v22pрeiD bwgꚷōqĬ׭oUvߵq+MRcg~NUfр|XCUsT눢T΋WB::ALE4e&3`rlߖlebVz;D1~@981z/sv,4EZ^mxQ;=*Dα4Ili! X '0GXjqD$Y$/dRnܿt#Ku8QGp^n귟NN).D %Q ɜ' ]{9GpДGPh8 1QH2E)%c76ݱ˧?tﯩv4j(DC4$ %1g8Hv:Ԏcn-x_ljZ9U#ǫQ-md~si >'g$9a/›_W}~t^epp1iah7,,P缯.&tkfTa0? h #Wс|W$M]wTVbTB:vgm8i:*dǗ SB/5E)}Si''i(/ C@sH"Ywί&'wYQ@a`N075?ޙ˶,*H%xnbs]Ewӈhvx~#zgdNk-]'kƴ0R@3'DińRE?PזqߥkS?¦Bk&OT~LI 3瀳 `8{@W)t~J_3ly8 a: (>OyC}\n-PխӍs10glU҈Ը: WT<ϟ}b;˒uۛyPr}J՗9. Ix'83 =wLȡ(@Ihiȕcn$=>%vh0Ĝ^M1/_PK!0R!ppt/slideLayouts/slideLayout5.xmlYn6?` xYTФЍÀ 6IvZIFRRl'N`c2y|.ZʦͪrKIf|b]\q^rr%ɻz[yz_UReO]WMm2EܾjYfUSĝڜO&PE>EMEa|j6Vɢe׃42;;vDwA*3z3V.Ӌ쏉e:7KuN<ʸP7&k֧U.m1>,+K50p2~47xrvúܟ(ҮTߓ7d~o2L&իꃻ4.4ri1qѢ'@ jbq@GaEt/it8-jֽIbǨ1E'$:Ϝ̣\ €0+8u1q9aTլb:wXHD[Vɧ*+EGOQSz~s2 w:9dCہ!FCM.{zJ?SWx/o*K?L8vA%dudc)P(z~ ,㸉_x2WƠL _M/89TE^"s]m KBBi!Z)P1zr]v՟ɼRCR4e0CRY*M%3*3/GDgٸkÐ}UC xBb"ۨj@%+T4) Xg #nbx,` !N͆=Vc l 3c`5Wswʶj]!4V^ z([5fncTea24 # )@][iʚyK򑖌۱ٍ`Ò1Q&xfݜlȤ*S+Khv~y8jtl+SWZΝyIz"ḀLAN [q"{UE(9ޗq 2uPkZw]Ww9/Ԣ &mDe]wmڲ9qmw^kZ{^m6vwr# Mlcs5;A#!9bAzm篽ҮZ nMTuPF4b*QsKƁPsmKEWVxȶے]+A _y/n, J hpgN6\qJɿvNë֞65U)j a[^ܘ?=d*U˶>ؖ1{X9d_jy 2f_K!ݿfÇR;6`23LSFP vid}uUV1.b{m͞v8^{cNVKkg9E9uE,1ʉe$Swz7F˙.J/ȄN<˓𲁦eӀyuT8b>ɑŁ별EAv74TU#f'N HF}Q!C54q^)nirC(.4Cig.&mys%nFn{N+?KRXvͧīpfAP ҧ$: jL8A &c$VC Z{nw߾ o\u3a,H10]!JGQNr1},>"dU>{^*AIcZZ@Ҙjz hz4ѹϿPK!/U !ppt/slideLayouts/slideLayout7.xmlVn6}/gZDQ"@עh[)fV9 )Nm E$G39sHy{Z gmꦿY?(If}_ތ/iK rӊ흔qj;6 #v7N-gݵXKxN6χj^Io]3N&l-IF@{ݲmi7qyikgRApfw1n+}*v⦗AO7fVǭ-k:l(j6Vjwo+v*TsqxN$ZV\XIIY_K$"nJPZ^XfOoU4Jp8J Ӱg-0:1YT/4@!2$M} KMo^@f(4^ էLQs1w"kvEG#-,/VQalşl"ѱV}M]E8}]Rc;nɖ?ֵ|xx{t.嗯VEn,EfUy:/̑!ʤJt2BhL*FWg>i<e{]Wm2EܾjQo)6㴉?wiqgh3ͲDU(D$oYj-Ш)u75r52Y <:'<5ʸAUv`|̺ĵdRlQ|QC./#K%@1? 0\`3Zj ;bNGP9Ī3fr{7ۃM=豞`1\Uˬ˅!7Jqv:E>GIPbϐ?a.u&ĎB96Zcߩk%MVER@Π %Y~)ơG&E4b!B#ߡN9#ܱ 9ZxXp]>QVP(Y׾nkD_Ly烮:GQumQc b.6u)!iRIF+0.yOUϙݴɅ9J`[5@k50(9 ϒFW":Mv1ހ$KXD^M,3XIj}z]j jG"1 Tq'Mc"fFGqq`>|tu 9^v(:6#ֶt9ԓ.sl{1 g=XCX j!@j!l|z?H({hYVe*L\,E~#9x9Ϛ a¨Z4p8>˲@mbsXlEn(ph8r(4-L l'm#`|&S[ {葁=xw OQ͹:Mfe Uj-BQ!*j8Ƿ;=Iq|[}c>Lmk!A !!Mh3Q@sBv|QkL[uVK{kwb[ٳVIEȇ1YrzoM;ΊZ;(_mpx4M0I]nZ;|EՐB QvhĻEJ%-F[tA.r |+'H9bJ ySn3j8CEtmSӲ|7A+uMq[%w5G$uy )8mB"aͷcr=N )ޚ8=fwv=1rLЄZCxʹ%dw|o߾.OQ*.'˞!6"nȢ#]ځi>216,ch-pNlJ6VZ#S~͛~T")TsԭZ BgPK!7ի$!ppt/slideLayouts/slideLayout9.xmlX[n8` xYQTФ0(H`.@X^#ѮӢ@5d.)ɏ&iǢˣ{yI?. k]^W̞XYi^]N^EHLNUuNnnߞ7']7RYQu'dTs2v"+Yd62V𵽞mbJlO8&|'YP'2TfE n7ִ݈Y0f~Jꦁj\*ˈb]eT̄KyqUJuӌZM͌Mlwxd[<3v0im0uhjD:Q@$Dp@2;.3=P b 'eP  wQ"z=:"}ȅg{.(g>!Ii`3faPwD?".9_BgcWs4rDs{f戏mG0}#>qx9jxu~0W9v{чx3zk&{*D(tųH21aЦTrVܳ+w |1]T\RPŒ pM=n;r&p2k;Y*C឴`REW36%tJVN7r>-k-]9OTnHa4UUX aT @֟0ye2\ {Vݙ4ΤUmD8ͼ6;q-|_ZkKta`~3oNoGf;έMWq&wX;Nz5Y˜GĠK YE,J<{1 \}VGy["b2Ҫ.c`D$L,@4q$~z5lZ3;:>w'mҼ]mtyGӹmq7W+uY 1Cש+*j-Qz{%eAMq%69Lr(Iw:x.6mU) J33B}D(fQ\F=L7].ݶZYBJ6ogԣk=PKf~W]זfHA?w1~.+Pb+ޏ7<1 kRmF$ G:CɔKĝ_hb&2iђ>#$]$fÑG'fOxRr88aeiH}N&pFfCn@: R+/RnM@)V_quLw<|#50LbcmKm*,aQbqB8vYZAomM?w$Tի,n[GxB (eԃ?2n0C1M$&^F2Qe&b4 Ea0k1C$q qeה6`ݡ׿'.~ިJK!?)& ep|BR:KR?0y3/N!R,:wUc-Ev,III㤭$@^L9CR~r֖څ O\mѕU{]f Am]˳{џuyﻍv8Zqb͛|8zުwN4T)EQnj.uji8f~ZUObVNsfo1z̾ I{Jdٖͣ[g*Ų.6oʴ*2ʘuwҘ \+/ af^*ɋL/&3nM9~5wJ4Uٱv [׿S b,nF{luz3/|jw74s4|3,CK32e C8H/z6?Yx! J]3fg  \AaF8 V)4##דؤΑmuR7MM&2) w[z)]rǺrgWV#Z<׬nA.?PuwނWlgq],Y:$Zm1ʋF1&xmy[Nh{uhP\&uR..LI,D8 bLG0DIL„O/O-u*xP)ODi%VC5nv*&A?_eɋN5/ו8!ì>#9cc>K?%UV|e$41&x 0 Sy/UT$yk}}&P+uw2f1e. $CpqH]E,JV*evoX SJF5gAAFj ͛Ŗu*yrD'X'04Eq؇嶒b?\͒=.tzYW%l{FDOaf AR B4"1?yzٽ?caiB H4mfД ݱ˷f`nzv )$ I(p?PK!ђ7-ppt/slideLayouts/_rels/slideLayout11.xml.relsϽ 0]&4uE$ۛт}\MxRbZV ț`5ܮgIÛr\h\xpEQ{g(]m(')#;+b cdk>-Ւ5R YdUUVռ3-N`~y+YzQml,I^,KdN:n$ns9*?yuV7|>&4-!Ҁѩŵ <~lx3 1C@VHF 4Yn z/j?GO"97rE+mFhVu62c'$QFClqiq/i +>:weeSŻ\vFK?A#ƑECXîoÐL`8|W5A7.:-(JMbbhja %d:l" ԥ G#!# I(rBE̊JzV 1Ef%-E-<)5aŋEV?ޠ* G[ď4ܵZ3eb{vQDaɥPko6Ek kM* 3a9*ߚ㹰;cW)ȒY_Ѣ_H嗰ȕ)lDr 28hqӶ"+D+m>t~7aH;9:6[{$[HزI8}1uNDqv/-Qf)&9gźĊY/T׿a}U}T^:a9gpli[#VL\'!OHrB>wA8VNLp"ȳ\vYɻ2Ye.bAxo`^hvH#e% /ht-qdj%XK-!Zi EW y1y ="qn˕v@$9#>PJꏩ?uUaܣzأtUۣtU1ڣ{wtIOUg{tn]BtpzQQcyPsa@q`]ma}N7En->jy*!+y[rt%27%mu%]ߝ>E{+֭W_; tٱv}>hQ*ڱcS1@7g "w,۝fT?_Զ??PK!ђ7,ppt/slideLayouts/_rels/slideLayout1.xml.relsϽ 0]&4uE$ۛт}\MxRbZV ț`5ܮgIÛr\h\xpEQ;8/zu"\gwdyv6MċOc^Lu֫Od/~,WW`:>ZP"ÓZp䖴67̗/Zk"NCEBj%,QwCej]゙7PhL!Dxk5K2dXO6]'i]Qq.r2:UkUQta;_^M>-3UtN'78ִ352hRiN:ۃ:''eXlVgv| qH%ގ g UxpPCr8&?>qrpU"-Fɢ"E14?M^\N~}=.Sb(vOlݿڪ\WYh_5WOI9Mcݵfԕ"7>&M29 Qm:*?RhN/DóuZ|P6ƷGNTؤO˲][0+l%qYj b`o!"cc(3Kyqбm{$4aZZ7ȂuȄE#(c9W. k-` A Fx9h=;460(8pTf$3dhB2,C6sBǨD)U-fĚW !>16*PK`w&GCP vs 42*0dHIBD(xV\ q55DK&u A:7)E P^Pk<q Y)"pAl0K"7`)ʡSz|l}8;Fa\("0 HqcXHaCk*[65N21# 9ҎJ ֽŔahz'LQ#m 2$#jDysîTl8t9}N$H'lg25 PdDr`6D6@1Fбez  4ݨkŒLYz$6 c$]t.PJ 25°|4ыڒ`A#$ƞ`S: :t+4#e|N<!I(֭jvq ׮2ec`5zMFW $x'hsH'ԳDuUbڡW\Ԍ(PsزF`ÝrIC 53yV#L Ykαͨv(]B(tM)ÌX+b \^.ƿ򩋲CZG񿆃8j4 [ե5Q^T([M<ş ᠺ'CufU7?xRf3M dhqYX{F >_o;8Įsdw7?T~'6.E\nb`=*fZ7'AQ -`:yBCw]_xX}ski(韊u(ɮ&eep𥌏xˢ ,uGPnaq9wXKnahC5Y=~pUͯl.ٴ~[Wc~D+dd^?Ϯ~t1Yn`<9T|P[W@W6|bHm6d:ʶ5gulm۱.|EMD&ͯYц-61<qx2iYu=岊j U[s6]E1i$m_/vw Hބ>HYqD^aŌR3P?r;72<*+p;':jۀxm ]/J#GK]۩E}IBv*,8H&$R%M 3꣎.RMzMJplR%fk}kh72"mܭWPɓ*kQF5d$΅l#FU3j-X#NFF 2qNFA:_qdlJ rQM!ԙAeeW(:~nqcKucKHϔ~O7 ώ8n:"cc&nTXmm5{8 =Q5zβ\" G:G\kLiuRk@1aSS1ڏJڪOFzF0%]{PQ W :5UN6=qTɍH2&q@ƃT<֌bn="=b +~|cWS@(7#Bdr+%'iDgM)?w'55A@RSoLj JT` sqInaLUx)p4@F##Dyh.B ?NsuV $&1%y:7IS!A\]ZPJɉ!ƘhXa]SHqS}J)L=})1S;=dcB2MH5qQGmkN),> b4wJ Uޤ%qQGMsT q~8*OiY Ra$rL@\B/dΣ4)5qQG퉣2K<ͰCZI @DXI oƨ$iRj⨉&zL|$h।Q{*Bk! m%FRō GM5qQ*l8dyAJdLTV?Z dI5qQG=zYΞ ֔zrdH pY\3yƅdF[36 䘋<eɴp*-J AҔP @{$,@Ҍf ,no DTsv|SўN%H oL `!OC 1P?vehc,8g@ͱAI3G0E$bP3,*`/cuL83OrjS `)Q=\S8x©+!Ju=ɩ cSoPвHx 40;Tk<Q i)rlhHntINMrjSbƂ2RÀ i9r9sEIzAlSĊߎʙLӘ*Q'b30s*rX㐦)AŒ/HJ_GM5qQ{RfVZFFS65S0GM5qQVXFG}>Gm>ee8?PK!<+Appt/diagrams/colors1.xml]o0'?D_][uiՏ!UI뮧$V; ~Ӏ8$P_6ym\\S͈TT #8@?ǟΑ4$@/Dˏ.8aTw$F,@NH HdH(c?l? B,!EE #o)4|R,>"EQL:; >`L:FE4Ռx3`B~Y_`J/{%3*W$5D&PnZ(ÄyRcEHU2vˤH60*S[^K״t(_[*("X%U΢d(ڲp^7M h̿.YzG-:Eяn1#) X@Y|:T dq _O->; 9!;TD4z_4d`ˆf2HO\fE':̛R8N60lsZ11cuqo0R% .wR`pـ\6e o7S"kvtU;cS;,j!"J ;S eڽwCB`iBv_c`ʎVg;'I1ǎr۔}IGeZ^[|8cu+hDpĆ8Vo\gl> ijݳ ]Nж;t5swY<8Tf!"]oxC-&<֡byrtygXK 8yyD#9XWIfs+}yc )c.PK!gf@Kppt/diagrams/layout1.xml[[s8~ߙ m|iٝINҝ}AvUȉ__]:dyI@Hߑs$Owa`Aq1ggSӀ;~ߘ4R "q7L흳/A R1=$=,N`D1 &h?q'a0@@i"l.BFq̎I캾 an99!L<"lafgC>qiNxS\b| (2Xj`/~lĶ\?A8 Ѥv=ZvQ؂?3މ1^4Q!#,sY`uAXBVڀ`ڙaTHǼ\JS?ܳg^@QC;i#gX3ɱc )f㞩1X̾*EP(6Hob<|woC7A޺ql%,~:ΠMУ# x0;u/f/F=0J0-W\}͎.wL:Dζ4c4 X8˯ 9SdBDvS6֬M#Lh @w@j  7!^*ς=?z,D[[-fq8`d/"qj.v%9ZNVDDp3YZek-qX,^p0sl<?!U- ~CգY-**iT}J{ GTšJ))-hH3̀z84~\#kݘouS:$G͙zZE_>9[VU 97k~_wpFZ7"JKKCg;8 pgUe[}we&8(^p_ քLDr[nŨne 6[Vңz7m[ʷ |(z˷:RU>$4ݥ7MԧV H$M3\ͫ>FVo|mo/A So"g+7k %W4ʷ#p FT6TYKZ9r]mr`=TґGaXKEIגxBz*3a*z_GAVW+?5?ѫ`/a]TVHҤ*]3\/*>F^Ve.8U_JEչRTJ׿Uzg_Gdצ\XqZo'PK!{C] ppt/theme/theme1.xmlY͋7? sw5'c;d7YkPa0J{}SYנdzZ2at oްY[3T9]|"b-Fw@;I8r14\(9%a7C 0\U:|?OGyrHl )>q2*husϞ=ߟ?z˹vP^??|ۏkNjYǝ?NЌgOLhtܑ\]2ˌ G"JaQHJbhq֚j(vӳ;O&x$ F.T MX8vR~v6!˹I89?vVʖ:itI&;ɄfK]>ɜSe(Ƀp^S8Uh7 I%iyX#ZvL't2>\:w{%-mJMw qKzJf3aMUI&j}w nȝl;%&4/ْ|ąc0/3Ѕ2DE̟ibА K%17&Dk$Y}S%_:2ғKF#t_m({»=>ɯ.pRQ_z#$$&8fYp*a1c%m#mUT`~x~4Q[|*(?ο:ϳxm#k)4{Ob~Fb4%Ȣ!h Th!h}EZЪTzfcjL@1c#A|>\Vɩsj$$Z)|B{ hIU1>">u~Sg.~5G-ES%rfpO|T}JaXhѣ2?Li @⊷s&r a yZW^MPK!dQppt/diagrams/data1.xmlPK-!R[t2z!:ppt/slideLayouts/slideLayout1.xmlPK-!W;T!2@ppt/slideLayouts/slideLayout2.xmlPK-!S?[!Dppt/slideLayouts/slideLayout3.xmlPK-!v!CJppt/slideLayouts/slideLayout4.xmlPK-!0R!6Oppt/slideLayouts/slideLayout5.xmlPK-!L !Uppt/slideLayouts/slideLayout6.xmlPK-!/U !Yppt/slideLayouts/slideLayout7.xmlPK-!EՎu!_]ppt/slideLayouts/slideLayout8.xmlPK-!7ի$!Scppt/slideLayouts/slideLayout9.xmlPK-! Fp"ippt/slideLayouts/slideLayout10.xmlPK-!!ܤ"mppt/slideLayouts/slideLayout11.xmlPK-!ђ7-rppt/slideLayouts/_rels/slideLayout11.xml.relsPK-!ђ7,sppt/slideLayouts/_rels/slideLayout5.xml.relsPK-!ђ7,tppt/slideLayouts/_rels/slideLayout6.xml.relsPK-!ђ7,uppt/slideLayouts/_rels/slideLayout4.xml.relsPK-!ђ7,vppt/slideLayouts/_rels/slideLayout3.xml.relsPK-!ђ7,wppt/slideLayouts/_rels/slideLayout7.xml.relsPK-!ђ7-xppt/slideLayouts/_rels/slideLayout10.xml.relsPK-!ђ7,yppt/slideLayouts/_rels/slideLayout9.xml.relsPK-!ђ7,zppt/slideLayouts/_rels/slideLayout8.xml.relsPK-!i_!,{ppt/slideMasters/_rels/slideMaster1.xml.relsPK-!߭h6!B}ppt/slideMasters/slideMaster1.xmlPK-!ђ7,ppt/slideLayouts/_rels/slideLayout1.xml.relsPK-!ђ7,ppt/slideLayouts/_rels/slideLayout2.xml.relsPK-!wppt/diagrams/drawing1.xmlPK-!<+AOppt/diagrams/colors1.xmlPK-!gf@Kppt/diagrams/layout1.xmlPK-!{C] Lppt/theme/theme1.xmlPK-!Yˤ QBppt/diagrams/quickStyle1.xmlPK-!d#k2ppt/presProps.xmlPK-!J3Jppt/viewProps.xmlPK-!ppt/tableStyles.xmlPK-!MIݯdocProps/app.xmlPK-!d `sections` - Sets the default of `titlesonly` to `true` - `jb-book`: - Maps the top-level `subtrees` to `parts` - Maps the top-level `entries` to `chapters` - Maps other levels of `entries` to `sections` - Sets the default of `titlesonly` to `true` For example: ```yaml defaults: titlesonly: true root: index subtrees: - entries: - file: doc1 entries: - file: doc2 ``` is equivalent to: ```yaml format: jb-book root: index parts: - chapters: - file: doc1 sections: - file: doc2 ``` :::{important} These change in key names do not change the output site-map structure. ::: ## Excluding files not in ToC By default, Sphinx will build all document files, regardless of whether they are specified in the Table of Contents, if they: 1. Have a file extension relating to a loaded parser (e.g. `.rst` or `.md`) 2. Do not match a pattern in [`exclude_patterns`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-exclude_patterns) To automatically add any document files that do not match a `file` or `glob` in the ToC to the `exclude_patterns` list, add to your `conf.py`: ```python external_toc_exclude_missing = True ``` Note that, for performance, files that are in *hidden folders* (e.g. in `.tox` or `.venv`) will not be added to `exclude_patterns` even if they are not specified in the ToC. You should exclude these folders explicitly. :::{important} This feature is not currently compatible with [orphan files](https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html#metadata). ::: sphinx-external-toc-1.0.1/pyproject.toml000066400000000000000000000036421453603135500203530ustar00rootroot00000000000000[build-system] requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" [project] name = "sphinx_external_toc" dynamic = ["version"] description = "A sphinx extension that allows the site-map to be defined in a single YAML file." readme = "README.md" authors = [{name = "Chris Sewell", email = "chrisj_sewell@hotmail.com"}] license = {file = "LICENSE"} classifiers = [ "Development Status :: 3 - Alpha", "Framework :: Sphinx :: Extension", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup", ] keywords = ["sphinx","extension", "toc"] requires-python = ">=3.9" dependencies = [ "click>=7.1", "pyyaml", "sphinx>=5", ] [project.urls] Homepage = "https://github.com/executablebooks/sphinx-external-toc" Documentation = "https://sphinx-external-toc.readthedocs.io" [project.entry-points] "jb.cmdline" = {toc = "sphinx_external_toc.cli:main"} [project.optional-dependencies] code_style = ["pre-commit>=2.12"] rtd = [ "myst-parser>=1.0.0", "sphinx-book-theme>=1.0.0", ] testing = [ "coverage", "pytest>=7.1", "pytest-cov", "pytest-regressions", ] [project.scripts] sphinx-etoc = "sphinx_external_toc.cli:main" [tool.flit.sdist] exclude = [ "docs/", "tests/", ] [tool.mypy] show_error_codes = true warn_unused_ignores = true warn_redundant_casts = true no_implicit_optional = true strict_equality = true [[tool.mypy.overrides]] module = ["docutils.*", "yaml.*"] ignore_missing_imports = true [tool.ruff.lint.isort] force-sort-within-sections = true sphinx-external-toc-1.0.1/sphinx_external_toc/000077500000000000000000000000001453603135500215125ustar00rootroot00000000000000sphinx-external-toc-1.0.1/sphinx_external_toc/__init__.py000066400000000000000000000022451453603135500236260ustar00rootroot00000000000000"""A sphinx extension that allows the project toctree to be defined in a single file.""" __version__ = "1.0.1" from typing import TYPE_CHECKING if TYPE_CHECKING: from sphinx.application import Sphinx def setup(app: "Sphinx") -> dict: """Initialize the Sphinx extension.""" from .events import ( InsertToctrees, TableofContents, add_changed_toctrees, ensure_index_file, parse_toc_to_env, ) # variables app.add_config_value("external_toc_path", "_toc.yml", "env") app.add_config_value("external_toc_exclude_missing", False, "env") # Note: this needs to occur after merge_source_suffix event (priority 800) # this cannot be a builder-inited event, since if we change the master_doc # it will always mark the config as changed in the env setup and re-build everything app.connect("config-inited", parse_toc_to_env, priority=900) app.connect("env-get-outdated", add_changed_toctrees) app.add_directive("tableofcontents", TableofContents) app.add_transform(InsertToctrees) app.connect("build-finished", ensure_index_file) return {"version": __version__, "parallel_read_safe": True} sphinx-external-toc-1.0.1/sphinx_external_toc/_compat.py000066400000000000000000000105401453603135500235060ustar00rootroot00000000000000"""Compatibility for using dataclasses instead of attrs.""" from __future__ import annotations import dataclasses as dc import re import sys from typing import Any, Callable, Pattern, Type from docutils.nodes import Element if sys.version_info >= (3, 10): DC_SLOTS: dict = {"slots": True} else: DC_SLOTS: dict = {} def field(**kwargs: Any): if sys.version_info < (3, 10): kwargs.pop("kw_only", None) if "validator" in kwargs: kwargs.setdefault("metadata", {})["validator"] = kwargs.pop("validator") return dc.field(**kwargs) field.__doc__ = dc.field.__doc__ def validate_fields(inst): """Validate the fields of a dataclass, according to `validator` functions set in the field metadata. This function should be called in the `__post_init__` of the dataclass. The validator function should take as input (inst, field, value) and raise an exception if the value is invalid. """ for field in dc.fields(inst): if "validator" not in field.metadata: continue if isinstance(field.metadata["validator"], list): for validator in field.metadata["validator"]: validator(inst, field, getattr(inst, field.name)) else: field.metadata["validator"](inst, field, getattr(inst, field.name)) ValidatorType = Callable[[Any, dc.Field, Any], None] def instance_of(type: Type[Any] | tuple[Type[Any], ...]) -> ValidatorType: """ A validator that raises a `TypeError` if the initializer is called with a wrong type for this particular attribute (checks are performed using `isinstance` therefore it's also valid to pass a tuple of types). :param type: The type to check for. """ def _validator(inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if not isinstance(value, type): raise TypeError( f"'{attr.name}' must be {type!r} (got {value!r} that is a {value.__class__!r})." ) return _validator def matches_re(regex: str | Pattern, flags: int = 0) -> ValidatorType: r""" A validator that raises `ValueError` if the initializer is called with a string that doesn't match *regex*. :param regex: a regex string or precompiled pattern to match against :param flags: flags that will be passed to the underlying re function (default 0) """ fullmatch = getattr(re, "fullmatch", None) if isinstance(regex, Pattern): if flags: raise TypeError( "'flags' can only be used with a string pattern; " "pass flags to re.compile() instead" ) pattern = regex else: pattern = re.compile(regex, flags) if fullmatch: match_func = pattern.fullmatch else: # Python 2 fullmatch emulation (https://bugs.python.org/issue16203) pattern = re.compile(r"(?:{})\Z".format(pattern.pattern), pattern.flags) match_func = pattern.match def _validator(inst, attr, value): if not match_func(value): raise ValueError( f"'{attr.name}' must match regex {pattern!r} ({value!r} doesn't)" ) return _validator def optional(validator: ValidatorType) -> ValidatorType: """ A validator that makes an attribute optional. An optional attribute is one which can be set to ``None`` in addition to satisfying the requirements of the sub-validator. """ def _validator(inst, attr, value): if value is None: return validator(inst, attr, value) return _validator def deep_iterable( member_validator: ValidatorType, iterable_validator: ValidatorType | None = None ) -> ValidatorType: """ A validator that performs deep validation of an iterable. :param member_validator: Validator to apply to iterable members :param iterable_validator: Validator to apply to iterable itself """ def _validator(inst, attr, value): if iterable_validator is not None: iterable_validator(inst, attr, value) for member in value: member_validator(inst, attr, member) return _validator # Docutils compatibility def findall(node: Element): # findall replaces traverse in docutils v0.18 # note a difference is that findall is an iterator return getattr(node, "findall", node.traverse) sphinx-external-toc-1.0.1/sphinx_external_toc/api.py000066400000000000000000000172031453603135500226400ustar00rootroot00000000000000"""Defines the `SiteMap` object, for storing the parsed ToC.""" from collections.abc import MutableMapping from dataclasses import asdict, dataclass from typing import Any, Dict, Iterator, List, Optional, Set, Union from ._compat import ( DC_SLOTS, deep_iterable, field, instance_of, matches_re, optional, validate_fields, ) #: Pattern used to match URL items. URL_PATTERN: str = r".+://.*" class FileItem(str): """A document path in a toctree list. This should be in POSIX format (folders split by ``/``), relative to the source directory, and can be with or without an extension. """ class GlobItem(str): """A document glob in a toctree list.""" @dataclass(**DC_SLOTS) class UrlItem: """A URL in a toctree.""" # regex should match sphinx.util.url_re url: str = field(validator=[instance_of(str), matches_re(URL_PATTERN)]) title: Optional[str] = field(default=None, validator=optional(instance_of(str))) def __post_init__(self): validate_fields(self) @dataclass(**DC_SLOTS) class TocTree: """An individual toctree within a document.""" # TODO validate uniqueness of docnames (at least one item) items: List[Union[GlobItem, FileItem, UrlItem]] = field( validator=deep_iterable( instance_of((GlobItem, FileItem, UrlItem)), instance_of(list) ) ) caption: Optional[str] = field( default=None, kw_only=True, validator=optional(instance_of(str)) ) hidden: bool = field(default=True, kw_only=True, validator=instance_of(bool)) maxdepth: int = field(default=-1, kw_only=True, validator=instance_of(int)) numbered: Union[bool, int] = field( default=False, kw_only=True, validator=instance_of((bool, int)) ) reversed: bool = field(default=False, kw_only=True, validator=instance_of(bool)) titlesonly: bool = field(default=False, kw_only=True, validator=instance_of(bool)) def __post_init__(self): validate_fields(self) def files(self) -> List[str]: """Returns a list of file items included in this ToC tree. :return: file items """ return [str(item) for item in self.items if isinstance(item, FileItem)] def globs(self) -> List[str]: """Returns a list of glob items included in this ToC tree. :return: glob items """ return [str(item) for item in self.items if isinstance(item, GlobItem)] @dataclass(**DC_SLOTS) class Document: """A document in the site map.""" # TODO validate uniqueness of docnames across all parts (and none should be the docname) docname: str = field(validator=instance_of(str)) subtrees: List[TocTree] = field( default_factory=list, validator=deep_iterable(instance_of(TocTree), instance_of(list)), ) title: Optional[str] = field(default=None, validator=optional(instance_of(str))) def __post_init__(self): validate_fields(self) def child_files(self) -> List[str]: """Return all children files. :return: child files """ return [name for tree in self.subtrees for name in tree.files()] def child_globs(self) -> List[str]: """Return all children globs. :return: child globs """ return [name for tree in self.subtrees for name in tree.globs()] class SiteMap(MutableMapping): """A mapping of documents to their toctrees (or None if terminal).""" def __init__( self, root: Document, meta: Optional[Dict[str, Any]] = None, file_format: Optional[str] = None, ) -> None: self._docs: Dict[str, Document] = {} self[root.docname] = root self._root: Document = root self._meta: Dict[str, Any] = meta or {} self._file_format = file_format @property def root(self) -> Document: """Return the root document of the ToC tree. :return: root document """ return self._root @property def meta(self) -> Dict[str, Any]: """Return the site-map metadata. :return: metadata dictionary """ return self._meta @property def file_format(self) -> Optional[str]: """Return the format of the file to write to. :return: output file format """ return self._file_format @file_format.setter def file_format(self, value: Optional[str]) -> None: """Set the format of the file to write to.""" self._file_format = value def globs(self) -> Set[str]: """Return set of all globs present across all toctrees.""" return {glob for item in self._docs.values() for glob in item.child_globs()} def __getitem__(self, docname: str) -> Document: """Enable retrieving a document by name using the indexing operator. :param docname: document name :return: document instance """ return self._docs[docname] def __setitem__(self, docname: str, item: Document) -> None: """Enable setting a document by name using the indexing operator. :param docname: document name :param item: document instance """ assert item.docname == docname self._docs[docname] = item def __delitem__(self, docname: str) -> None: """Enable removing a document by name. :param docname: document name """ assert docname != self._root.docname, "cannot delete root doc item" del self._docs[docname] def __iter__(self) -> Iterator[str]: """Enable iterating the names of the documents the site map is composed of. :yield: document name """ for docname in self._docs: yield docname def __len__(self) -> int: """Enable using Python's built-in `len()` function to return the number of documents contained in a site map. :return: number of documents in this site map """ return len(self._docs) def as_json(self) -> Dict[str, Any]: """Return JSON serialized site-map representation.""" doc_dict = { k: asdict(self._docs[k]) if self._docs[k] else self._docs[k] for k in sorted(self._docs) } def _replace_items(d: Dict[str, Any]) -> Dict[str, Any]: for k, v in d.items(): if isinstance(v, dict): d[k] = _replace_items(v) elif isinstance(v, (list, tuple)): d[k] = [ _replace_items(i) if isinstance(i, dict) else (str(i) if isinstance(i, str) else i) for i in v ] elif isinstance(v, str): d[k] = str(v) return d doc_dict = _replace_items(doc_dict) data = { "root": self.root.docname, "documents": doc_dict, "meta": self.meta, } if self.file_format: data["file_format"] = self.file_format return data def get_changed(self, previous: "SiteMap") -> Set[str]: """Compare this sitemap to another and return a list of changed documents. .. note:: for Sphinx, file extensions should be removed to get docnames. """ changed_docs = set() # check if the root document has changed if self.root != previous.root: changed_docs.add(self.root.docname) for name, doc in self._docs.items(): if name not in previous: changed_docs.add(name) continue prev_doc = previous[name] if prev_doc != doc: changed_docs.add(name) return changed_docs sphinx-external-toc-1.0.1/sphinx_external_toc/cli.py000066400000000000000000000106741453603135500226430ustar00rootroot00000000000000from pathlib import Path, PurePosixPath import click import yaml from sphinx_external_toc import __version__ from sphinx_external_toc.parsing import FILE_FORMATS, create_toc_dict, parse_toc_yaml from sphinx_external_toc.tools import ( create_site_from_toc, create_site_map_from_path, migrate_jupyter_book, ) @click.group(context_settings={"help_option_names": ["-h", "--help"]}) @click.version_option(version=__version__) def main(): """Command-line for sphinx-external-toc.""" @main.command("parse") @click.argument("toc_file", type=click.Path(exists=True, file_okay=True)) def parse_toc(toc_file): """Parse a ToC file to a site-map YAML.""" site_map = parse_toc_yaml(toc_file) click.echo(yaml.dump(site_map.as_json(), sort_keys=False, default_flow_style=False)) @main.command("to-project") @click.argument("toc_file", type=click.Path(exists=True, file_okay=True)) @click.option( "-p", "--path", default=None, type=click.Path(exists=False, file_okay=False, dir_okay=True), help="The root directory [default: ToC file directory].", ) @click.option( "-e", "--extension", type=click.Choice(["rst", "md"]), default="rst", show_default=True, help="The default file extension to use.", ) @click.option("-o", "--overwrite", is_flag=True, help="Overwrite existing files.") def create_site(toc_file, path, extension, overwrite): """Create a project directory from a ToC file.""" create_site_from_toc( toc_file, root_path=path, default_ext="." + extension, overwrite=overwrite ) # TODO option to add basic conf.py? click.secho("SUCCESS!", fg="green") @main.command("from-project") @click.argument( "site_dir", type=click.Path(exists=True, file_okay=False, dir_okay=True) ) @click.option( "-e", "--extension", multiple=True, default=[".rst", ".md"], show_default=True, help="File extensions to consider as documents (use multiple times)", ) @click.option( "-i", "--index", default="index", show_default=True, help="File name (without suffix) considered as the index file in a folder", ) @click.option( "-s", "--skip-match", multiple=True, default=[".*"], show_default=True, help="File/Folder names which match will be ignored (use multiple times)", ) @click.option( "-t", "--guess-titles", is_flag=True, help="Guess titles of documents from path names", ) @click.option( "-f", "--file-format", type=click.Choice(list(FILE_FORMATS)), default=list(FILE_FORMATS)[0], show_default=True, help="The key-mappings to use.", ) def create_toc(site_dir, extension, index, skip_match, guess_titles, file_format): """Create a ToC file from a project directory.""" site_map = create_site_map_from_path( site_dir, suffixes=extension, default_index=index, ignore_matches=skip_match, file_format=file_format, ) if guess_titles: for docname in site_map: # don't give a title to the root document if docname == site_map.root.docname: continue filepath = PurePosixPath(docname) # use the folder name for index files name = filepath.parent.name if filepath.name == index else filepath.name # split into words words = name.split("_") # remove first word if is an integer words = words[1:] if words and all(c.isdigit() for c in words[0]) else words site_map[docname].title = " ".join(words).capitalize() data = create_toc_dict(site_map) click.echo(yaml.dump(data, sort_keys=False, default_flow_style=False)) @main.command("migrate") @click.argument("toc_file", type=click.Path(exists=True, file_okay=True)) @click.option( "-f", "--format", type=click.Choice(["jb-v0.10"]), help="The format to migrate from.", ) @click.option( "-o", "--output", type=click.Path(allow_dash=True, exists=False, file_okay=True, dir_okay=False), help="Write to a file path.", ) def migrate_toc(toc_file, format, output): """Migrate a ToC from a previous revision.""" toc = migrate_jupyter_book(Path(toc_file)) content = yaml.dump(toc, sort_keys=False, default_flow_style=False) if output: path = Path(output) path.parent.mkdir(exist_ok=True, parents=True) path.write_text(content, encoding="utf8") click.secho(f"Written to: {path}", fg="green") else: click.echo(content) sphinx-external-toc-1.0.1/sphinx_external_toc/events.py000066400000000000000000000320111453603135500233650ustar00rootroot00000000000000"""Sphinx event functions and directives.""" import glob from pathlib import Path, PurePosixPath from typing import Any, List, Optional, Set from docutils import nodes from sphinx.addnodes import toctree as toctree_node from sphinx.application import Sphinx from sphinx.config import Config from sphinx.environment import BuildEnvironment from sphinx.errors import ExtensionError from sphinx.transforms import SphinxTransform from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.matching import Matcher, patfilter, patmatch from ._compat import findall from .api import Document, FileItem, GlobItem, SiteMap, UrlItem from .parsing import parse_toc_yaml logger = logging.getLogger(__name__) def create_warning( app: Sphinx, doctree: nodes.document, category: str, message: str, *, line: Optional[int] = None, append_to: Optional[nodes.Element] = None, wtype: str = "etoc", ) -> Optional[nodes.system_message]: """Generate a warning, logging it if necessary. If the warning type is listed in the ``suppress_warnings`` configuration, then ``None`` will be returned and no warning logged. """ message = f"{message} [{wtype}.{category}]" kwargs = {"line": line} if line is not None else {} if not logging.is_suppressed_warning(wtype, category, app.config.suppress_warnings): msg_node = doctree.reporter.warning(message, **kwargs) if append_to is not None: append_to.append(msg_node) return msg_node return None def remove_suffix(docname: str, suffixes: List[str]) -> str: """Remove any suffixes.""" for suffix in suffixes: if docname.endswith(suffix): return docname[: -len(suffix)] return docname def parse_toc_to_env(app: Sphinx, config: Config) -> None: """Parse the external toc file and store it in the Sphinx environment. Also, change the ``master_doc`` and add to ``exclude_patterns`` if necessary. """ # TODO this seems to work in the tests, but I still want to double check external_toc_path = PurePosixPath(app.config["external_toc_path"]) if not external_toc_path.is_absolute(): path = Path(app.srcdir) / str(external_toc_path) else: path = Path(str(external_toc_path)) if not path.exists(): raise ExtensionError(f"[etoc] `external_toc_path` does not exist: {path}") if not path.is_file(): raise ExtensionError(f"[etoc] `external_toc_path` is not a file: {path}") try: site_map = parse_toc_yaml(path) except Exception as exc: raise ExtensionError(f"[etoc] {exc}") from exc config.external_site_map = site_map # Update the master_doc to the root doc of the site map root_doc = remove_suffix(site_map.root.docname, config.source_suffix) if config["master_doc"] != root_doc: logger.info("[etoc] Changing master_doc to '%s'", root_doc) config["master_doc"] = root_doc if config["external_toc_exclude_missing"]: # add files not specified in ToC file to exclude list new_excluded: List[str] = [] already_excluded = Matcher(config["exclude_patterns"]) for suffix in config["source_suffix"]: # recurse files in source directory, with this suffix, note # we do not use `Path.glob` here, since it does not ignore hidden files: # https://stackoverflow.com/questions/49862648/why-do-glob-glob-and-pathlib-path-glob-treat-hidden-files-differently for path_str in glob.iglob( str(Path(app.srcdir) / "**" / f"*{suffix}"), recursive=True ): path = Path(path_str) if not path.is_file(): continue posix = path.relative_to(app.srcdir).as_posix() posix_no_suffix = posix[: -len(suffix)] components = posix.split("/") if not ( # files can be stored with or without suffixes posix in site_map or posix_no_suffix in site_map # ignore anything already excluded, we have to check against # the file path and all its sub-directory paths or any( already_excluded("/".join(components[: i + 1])) for i in range(len(components)) ) # don't exclude docnames matching globs or any(patmatch(posix_no_suffix, pat) for pat in site_map.globs()) ): new_excluded.append(posix) if new_excluded: logger.info( "[etoc] Excluded %s extra file(s) not in toc", len(new_excluded) ) logger.debug("[etoc] Excluded extra file(s) not in toc: %r", new_excluded) # Note, don't `extend` list, as it alters the default `Config.config_values` config["exclude_patterns"] = config["exclude_patterns"] + new_excluded def add_changed_toctrees( app: Sphinx, env: BuildEnvironment, added: Set[str], changed: Set[str], removed: Set[str], ) -> Set[str]: """Add docs with new or changed toctrees to changed list.""" previous_map = getattr(app.env, "external_site_map", None) # move external_site_map from config to env site_map: SiteMap app.env.external_site_map = site_map = app.config.external_site_map # Compare to previous map, to record docnames with new or changed toctrees if not previous_map: return set() filenames = site_map.get_changed(previous_map) return {remove_suffix(name, app.config.source_suffix) for name in filenames} class TableOfContentsNode(nodes.Element): """A placeholder for the insertion of a toctree (in ``insert_toctrees``).""" def __init__(self, **attributes: Any) -> None: super().__init__(rawsource="", **attributes) class TableofContents(SphinxDirective): """Insert a placeholder for toctree insertion.""" # TODO allow for name option of tableofcontents (to reference it) def run(self) -> List[TableOfContentsNode]: """Insert a ``TableOfContentsNode`` node.""" node = TableOfContentsNode() self.set_source_info(node) return [node] def insert_toctrees(app: Sphinx, doctree: nodes.document) -> None: """Create the toctree nodes and add it to the document. Adapted from `sphinx/directives/other.py::TocTree` """ # check for existing toctrees and raise warning for node in findall(doctree)(toctree_node): create_warning( app, doctree, "toctree", "toctree directive not expected with external-toc", line=node.line, ) toc_placeholders: List[TableOfContentsNode] = list( findall(doctree)(TableOfContentsNode) ) site_map: SiteMap = app.env.external_site_map doc_item: Optional[Document] = site_map.get(app.env.docname) # check for matches with suffix # TODO check in sitemap, that we do not have multiple docs of the same name # (strip extensions on creation) for suffix in app.config.source_suffix: if doc_item is not None: break doc_item = site_map.get(app.env.docname + suffix) if doc_item is None or not doc_item.subtrees: if toc_placeholders: create_warning( app, doctree, "tableofcontents", "tableofcontents directive in document with no descendants", ) for node in toc_placeholders: node.replace_self([]) return # TODO allow for more than one tableofcontents, i.e. per part? for node in toc_placeholders[1:]: create_warning( app, doctree, "tableofcontents", "more than one tableofcontents directive in document", line=node.line, ) node.replace_self([]) # initial variables all_docnames = app.env.found_docs.copy() all_docnames.remove(app.env.docname) # remove current document excluded = Matcher(app.config.exclude_patterns) node_list: List[nodes.Element] = [] for toctree in doc_item.subtrees: subnode = toctree_node() subnode["parent"] = app.env.docname subnode.source = doctree["source"] subnode.line = 1 subnode["entries"] = [] subnode["includefiles"] = [] subnode["maxdepth"] = toctree.maxdepth subnode["caption"] = toctree.caption # TODO this wasn't in the original code, # but alabaster theme intermittently raised `KeyError('rawcaption')` subnode["rawcaption"] = toctree.caption or "" subnode["glob"] = any(isinstance(entry, GlobItem) for entry in toctree.items) subnode["hidden"] = False if toc_placeholders else toctree.hidden subnode["includehidden"] = False subnode["numbered"] = ( 0 if toctree.numbered is False else (999 if toctree.numbered is True else int(toctree.numbered)) ) subnode["titlesonly"] = toctree.titlesonly wrappernode = nodes.compound(classes=["toctree-wrapper"]) wrappernode.append(subnode) for entry in toctree.items: if isinstance(entry, UrlItem): subnode["entries"].append((entry.title, entry.url)) elif isinstance(entry, FileItem): child_doc_item = site_map[entry] docname = str(entry) title = child_doc_item.title docname = remove_suffix(docname, app.config.source_suffix) if docname not in app.env.found_docs: if excluded(app.env.doc2path(docname, None)): message = f"toctree contains reference to excluded document {docname!r}" else: message = f"toctree contains reference to nonexisting document {docname!r}" create_warning(app, doctree, "ref", message, append_to=node_list) app.env.note_reread() else: subnode["entries"].append((title, docname)) subnode["includefiles"].append(docname) elif isinstance(entry, GlobItem): patname = str(entry) docnames = sorted(patfilter(all_docnames, patname)) for docname in docnames: all_docnames.remove(docname) # don't include it again subnode["entries"].append((None, docname)) subnode["includefiles"].append(docname) if not docnames: message = ( f"toctree glob pattern '{entry}' didn't match any documents" ) create_warning(app, doctree, "glob", message, append_to=node_list) # reversing entries can be useful when globbing if toctree.reversed: subnode["entries"] = list(reversed(subnode["entries"])) subnode["includefiles"] = list(reversed(subnode["includefiles"])) node_list.append(wrappernode) if toc_placeholders: toc_placeholders[0].replace_self(node_list) elif doctree.children and isinstance(doctree.children[-1], nodes.section): # note here the toctree cannot not just be appended to the end of the doc, # since `TocTreeCollector.process_doc` expects it in a section # otherwise it will result in the child documents being on the same level as this document # TODO check if there is this is always ok doctree.children[-1].extend(node_list) else: doctree.children.extend(node_list) class InsertToctrees(SphinxTransform): """Create the toctree nodes and add it to the document. This needs to occur at least before the ``DoctreeReadEvent`` (priority 880), which triggers the `TocTreeCollector.process_doc` event (priority 500) """ default_priority = 100 def apply(self, **kwargs: Any) -> None: insert_toctrees(self.app, self.document) def ensure_index_file(app: Sphinx, exception: Optional[Exception]) -> None: """Ensure that an index.html exists for HTML builds. This is required when navigating to the site, without specifying a page, which will then route to index.html by default. """ index_path = Path(app.outdir).joinpath("index.html") if ( exception is not None or "html" not in app.builder.format or app.config.master_doc == "index" # TODO: rewrite the redirect if master_doc has changed since last build or index_path.exists() ): return root_name = remove_suffix(app.config.master_doc, app.config.source_suffix) if app.builder.name == "dirhtml": redirect_url = f"{root_name}/index.html" else: # Assume a single index for all non dir-HTML builders redirect_url = f"{root_name}.html" redirect_text = f'\n' index_path.write_text(redirect_text, encoding="utf8") logger.info("[etoc] missing index.html written as redirect to '%s.html'", root_name) sphinx-external-toc-1.0.1/sphinx_external_toc/parsing.py000066400000000000000000000345571453603135500235450ustar00rootroot00000000000000"""Parse the ToC to a `SiteMap` object.""" from collections.abc import Mapping from dataclasses import dataclass, fields from pathlib import Path from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union import yaml from ._compat import DC_SLOTS, field from .api import Document, FileItem, GlobItem, SiteMap, TocTree, UrlItem DEFAULT_SUBTREES_KEY = "subtrees" DEFAULT_ITEMS_KEY = "entries" FILE_FORMAT_KEY = "format" ROOT_KEY = "root" FILE_KEY = "file" GLOB_KEY = "glob" URL_KEY = "url" TOCTREE_OPTIONS = ( "caption", "hidden", "maxdepth", "numbered", "reversed", "titlesonly", ) @dataclass(**DC_SLOTS) class FileFormat: """Mapping of keys for subtrees and items, dependant on depth in the ToC.""" toc_defaults: Dict[str, Any] = field(default_factory=dict) subtrees_keys: Sequence[str] = () items_keys: Sequence[str] = () default_subtrees_key: str = DEFAULT_SUBTREES_KEY default_items_key: str = DEFAULT_ITEMS_KEY def get_subtrees_key(self, depth: int) -> str: """Get the subtrees key name for this depth in the ToC. :param depth: recursive depth (starts at 0) :return: subtrees key name """ try: return self.subtrees_keys[depth] except IndexError: return self.default_subtrees_key def get_items_key(self, depth: int) -> str: """Get the items key name for this depth in the ToC. :param depth: recursive depth (starts at 0) :return: items key name """ try: return self.items_keys[depth] except IndexError: return self.default_items_key FILE_FORMATS: Dict[str, FileFormat] = { "default": FileFormat(), "jb-book": FileFormat( subtrees_keys=("parts",), items_keys=("chapters",), default_items_key="sections", toc_defaults={"titlesonly": True}, ), "jb-article": FileFormat( default_items_key="sections", toc_defaults={"titlesonly": True}, ), } class MalformedError(Exception): """Raised if the `_toc.yml` file is malformed.""" def parse_toc_yaml(path: Union[str, Path], encoding: str = "utf8") -> SiteMap: """Parse the ToC file. :param path: `_toc.yml` file path :param encoding: `_toc.yml` file character encoding :return: parsed site map """ with Path(path).open(encoding=encoding) as handle: data = yaml.safe_load(handle) return parse_toc_data(data) def parse_toc_data(data: Dict[str, Any]) -> SiteMap: """Parse a dictionary of the ToC. :param data: ToC data dictionary :return: parsed site map """ if not isinstance(data, Mapping): raise MalformedError(f"toc is not a mapping: {type(data)}") try: file_format = FILE_FORMATS[data.get(FILE_FORMAT_KEY, "default")] except KeyError: raise MalformedError( f"'{FILE_FORMAT_KEY}' key value not recognised: " f"'{data.get(FILE_FORMAT_KEY, 'default')}'" ) defaults: Dict[str, Any] = { **file_format.toc_defaults, **data.get("defaults", {}), } doc_item, docs_list = _parse_doc_item( data, defaults, "/", depth=0, is_root=True, file_format=file_format ) site_map = SiteMap( root=doc_item, meta=data.get("meta"), file_format=data.get(FILE_FORMAT_KEY), ) _parse_docs_list(docs_list, site_map, defaults, depth=1, file_format=file_format) return site_map def _parse_doc_item( data: Dict[str, Any], defaults: Dict[str, Any], path: str, *, depth: int, file_format: FileFormat, is_root: bool = False, ) -> Tuple[Document, Sequence[Tuple[str, Dict[str, Any]]]]: """Parse a single doc item. :param data: doc item dictionary :param defaults: doc item defaults dictionary :param path: doc item file path :param depth: recursive depth (starts at 0) :param file_format: doc item file format :param is_root: whether this is the root item, defaults to False :raises MalformedError: invalid doc item :return: parsed doc item """ file_key = ROOT_KEY if is_root else FILE_KEY if file_key not in data: raise MalformedError(f"'{file_key}' key not found @ '{path}'") subtrees_key = file_format.get_subtrees_key(depth) items_key = file_format.get_items_key(depth) # check no unknown keys present allowed_keys = { file_key, "title", "options", subtrees_key, items_key, # top-level only FILE_FORMAT_KEY, "defaults", "meta", } if not allowed_keys.issuperset(data.keys()): unknown_keys = set(data.keys()).difference(allowed_keys) raise MalformedError( f"Unknown keys found: {unknown_keys!r}, allowed: {allowed_keys!r} @ '{path}'" ) shorthand_used = False if items_key in data: # this is a shorthand for defining a single subtree if subtrees_key in data: raise MalformedError( f"Both '{subtrees_key}' and '{items_key}' found @ '{path}'" ) subtrees_data = [{items_key: data[items_key], **data.get("options", {})}] shorthand_used = True elif subtrees_key in data: subtrees_data = data[subtrees_key] if not (isinstance(subtrees_data, Sequence) and subtrees_data): raise MalformedError(f"'{subtrees_key}' not a non-empty list @ '{path}'") path = f"{path}{subtrees_key}/" else: subtrees_data = [] _known_link_keys = {FILE_KEY, GLOB_KEY, URL_KEY} toctrees = [] for toc_idx, toc_data in enumerate(subtrees_data): toc_path = path if shorthand_used else f"{path}{toc_idx}/" if not (isinstance(toc_data, Mapping) and items_key in toc_data): raise MalformedError( f"entry not a mapping containing '{items_key}' key @ '{toc_path}'" ) items_data = toc_data[items_key] if not (isinstance(items_data, Sequence) and items_data): raise MalformedError(f"'{items_key}' not a non-empty list @ '{toc_path}'") # generate items list items: List[Union[GlobItem, FileItem, UrlItem]] = [] for item_idx, item_data in enumerate(items_data): if not isinstance(item_data, Mapping): raise MalformedError( f"entry not a mapping type @ '{toc_path}{items_key}/{item_idx}'" ) link_keys = _known_link_keys.intersection(item_data) # validation checks if not link_keys: raise MalformedError( f"entry does not contain one of " f"{_known_link_keys!r} @ '{toc_path}{items_key}/{item_idx}'" ) if not len(link_keys) == 1: raise MalformedError( f"entry contains incompatible keys " f"{link_keys!r} @ '{toc_path}{items_key}/{item_idx}'" ) for item_key in (GLOB_KEY, URL_KEY): for other_key in (subtrees_key, items_key): if link_keys == {item_key} and other_key in item_data: raise MalformedError( f"entry contains incompatible keys " f"'{item_key}' and '{other_key}' @ '{toc_path}{items_key}/{item_idx}'" ) try: if link_keys == {FILE_KEY}: items.append(FileItem(item_data[FILE_KEY])) elif link_keys == {GLOB_KEY}: items.append(GlobItem(item_data[GLOB_KEY])) elif link_keys == {URL_KEY}: items.append(UrlItem(item_data[URL_KEY], item_data.get("title"))) except (ValueError, TypeError) as exc: exc_arg = exc.args[0] if exc.args else "" raise MalformedError( f"entry validation @ '{toc_path}{items_key}/{item_idx}': {exc_arg}" ) from exc # generate toc key-word arguments keywords = {k: toc_data[k] for k in TOCTREE_OPTIONS if k in toc_data} for key in defaults: if key not in keywords: keywords[key] = defaults[key] try: toc_item = TocTree(items=items, **keywords) except (ValueError, TypeError) as exc: exc_arg = exc.args[0] if exc.args else "" raise MalformedError( f"toctree validation @ '{toc_path}': {exc_arg}" ) from exc toctrees.append(toc_item) try: doc_item = Document( docname=data[file_key], title=data.get("title"), subtrees=toctrees ) except (ValueError, TypeError) as exc: exc_arg = exc.args[0] if exc.args else "" raise MalformedError(f"doc validation @ '{path}': {exc_arg}") from exc # list of docs that need to be parsed recursively (and path) docs_to_be_parsed_list = [ ( f"{path}/{items_key}/{ii}/" if shorthand_used else f"{path}{ti}/{items_key}/{ii}/", item_data, ) for ti, toc_data in enumerate(subtrees_data) for ii, item_data in enumerate(toc_data[items_key]) if FILE_KEY in item_data ] return ( doc_item, docs_to_be_parsed_list, ) def _parse_docs_list( docs_list: Sequence[Tuple[str, Dict[str, Any]]], site_map: SiteMap, defaults: Dict[str, Any], *, depth: int, file_format: FileFormat, ): """Parse a list of docs. :param docs_list: sequence of doc items :param site_map: site map :param defaults: default doc item values :param depth: recursive depth (starts at 0) :param file_format: doc item file format :raises MalformedError: doc file used multiple times """ for child_path, doc_data in docs_list: docname = doc_data[FILE_KEY] if docname in site_map: raise MalformedError(f"document file used multiple times: '{docname}'") child_item, child_docs_list = _parse_doc_item( doc_data, defaults, child_path, depth=depth, file_format=file_format, ) site_map[docname] = child_item _parse_docs_list( child_docs_list, site_map, defaults, depth=depth + 1, file_format=file_format, ) def create_toc_dict(site_map: SiteMap, *, skip_defaults: bool = True) -> Dict[str, Any]: """Create the ToC dictionary from a site-map. :param site_map: site map :param skip_defaults: do not add key/values for values that are already the default :raises KeyError: invalid file format :return: ToC dictionary """ try: file_format = FILE_FORMATS[site_map.file_format or "default"] except KeyError: raise KeyError(f"File format not recognised @ '{site_map.file_format}'") data = _docitem_to_dict( site_map.root, site_map, depth=0, skip_defaults=skip_defaults, is_root=True, file_format=file_format, ) if site_map.meta: data["meta"] = site_map.meta.copy() if site_map.file_format and site_map.file_format != "default": # ensure it is the first key data = {FILE_FORMAT_KEY: site_map.file_format, **data} return data def _docitem_to_dict( doc_item: Document, site_map: SiteMap, *, depth: int, file_format: FileFormat, skip_defaults: bool = True, is_root: bool = False, parsed_docnames: Optional[Set[str]] = None, ) -> Dict[str, Any]: """Create ToC dictionary from a `Document` and a `SiteMap`. :param doc_item: document instance :param site_map: site map :param depth: recursive depth (starts at 0) :param file_format: doc item file format :param skip_defaults: do not add key/values for values that are already the default :param is_root: whether this is the root item, defaults to False :param parsed_docnames: parsed document names cache used to prevent infinite recursion :raises RecursionError: site map recursion :raises TypeError: invalid ToC item :return: parsed ToC dictionary """ file_key = ROOT_KEY if is_root else FILE_KEY subtrees_key = file_format.get_subtrees_key(depth) items_key = file_format.get_items_key(depth) # protect against infinite recursion parsed_docnames = parsed_docnames or set() if doc_item.docname in parsed_docnames: raise RecursionError(f"{doc_item.docname!r} in site-map multiple times") parsed_docnames.add(doc_item.docname) data: Dict[str, Any] = {} data[file_key] = doc_item.docname if doc_item.title is not None: data["title"] = doc_item.title if not doc_item.subtrees: return data def _parse_item(item): if isinstance(item, FileItem): if item in site_map: return _docitem_to_dict( site_map[item], site_map, depth=depth + 1, file_format=file_format, skip_defaults=skip_defaults, parsed_docnames=parsed_docnames, ) return {FILE_KEY: str(item)} if isinstance(item, GlobItem): return {GLOB_KEY: str(item)} if isinstance(item, UrlItem): if item.title is not None: return {URL_KEY: item.url, "title": item.title} return {URL_KEY: item.url} raise TypeError(item) data[subtrees_key] = [] # TODO handle default_factory _defaults = {f.name: f.default for f in fields(TocTree)} for toctree in doc_item.subtrees: # only add these keys if their value is not the default toctree_data = { key: getattr(toctree, key) for key in TOCTREE_OPTIONS if (not skip_defaults) or getattr(toctree, key) != _defaults[key] } toctree_data[items_key] = [_parse_item(s) for s in toctree.items] data[subtrees_key].append(toctree_data) # apply shorthand if possible (one toctree in subtrees) if len(data[subtrees_key]) == 1 and items_key in data[subtrees_key][0]: old_toctree_data = data.pop(subtrees_key)[0] # move options to options key if len(old_toctree_data) > 1: data["options"] = { k: v for k, v in old_toctree_data.items() if k != items_key } data[items_key] = old_toctree_data[items_key] return data sphinx-external-toc-1.0.1/sphinx_external_toc/py.typed000066400000000000000000000000321453603135500232040ustar00rootroot00000000000000# Marker file for PEP 561 sphinx-external-toc-1.0.1/sphinx_external_toc/tools.py000066400000000000000000000323171453603135500232320ustar00rootroot00000000000000import re import shutil from fnmatch import fnmatch from itertools import chain from pathlib import Path, PurePosixPath from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union import yaml from .api import Document, FileItem, SiteMap, TocTree from .parsing import ( DEFAULT_ITEMS_KEY, DEFAULT_SUBTREES_KEY, MalformedError, create_toc_dict, parse_toc_data, parse_toc_yaml, ) def create_site_from_toc( toc_path: Union[str, Path], *, root_path: Union[None, str, Path] = None, default_ext: str = ".rst", encoding: str = "utf8", overwrite: bool = False, toc_name: Optional[str] = "_toc.yml", ) -> SiteMap: """Create the files defined in the external toc file. Additional files can also be created by defining them in `meta`/`create_files` of the toc. Text can also be appended to files, by defining them in `meta`/`create_append` (as a mapping from files to text). :param toc_path: path to ToC file :param root_path: the root directory, or use ToC file directory :param default_ext: default file extension to use :param encoding: encoding for writing files :param overwrite: overwrite existing files (otherwise raise ``IOError``) :param toc_name: copy ToC file to root with this name """ assert default_ext in {".rst", ".md"} site_map = parse_toc_yaml(toc_path) root_path = Path(toc_path).parent if root_path is None else Path(root_path) root_path.mkdir(parents=True, exist_ok=True) # retrieve and validate meta variables additional_files = site_map.meta.get("create_files", []) assert isinstance(additional_files, Sequence), "'create_files' should be a list" append_text = site_map.meta.get("create_append", {}) assert isinstance(append_text, Mapping), "'create_append' should be a mapping" # copy toc file to root if toc_name and not root_path.joinpath(toc_name).exists(): shutil.copyfile(toc_path, root_path.joinpath(toc_name)) # create files for docname in chain(site_map, additional_files): # create document filename = docname if not any(docname.endswith(ext) for ext in {".rst", ".md"}): filename += default_ext docpath = root_path.joinpath(PurePosixPath(filename)) if docpath.exists() and not overwrite: raise IOError(f"Path already exists: {docpath}") docpath.parent.mkdir(parents=True, exist_ok=True) content = [] # add heading based on file type heading = f"Heading: {filename}" if filename.endswith(".rst"): content = [heading, "=" * len(heading), ""] elif filename.endswith(".md"): content = ["# " + heading, ""] # append extra text extra_lines = append_text.get(docname, "").splitlines() if extra_lines: content.extend(extra_lines + [""]) # note \n works when writing for all platforms: # https://docs.python.org/3/library/os.html#os.linesep docpath.write_text("\n".join(content), encoding=encoding) return site_map def create_site_map_from_path( root_path: Union[str, Path], *, suffixes: Sequence[str] = (".rst", ".md"), default_index: str = "index", ignore_matches: Sequence[str] = (".*",), file_format: Optional[str] = None, ) -> SiteMap: """Create the site-map from a folder structure. Files and folders are sorted in natural order, see: https://en.wikipedia.org/wiki/Natural_sort_order. :param suffixes: file suffixes to consider as documents :param default_index: file name (without suffix) considered as the index file for a folder, if not found then the first file is taken as the index :param ignore_matches: file/folder names which match one of these will be ignored, uses fnmatch Unix shell-style wildcards, defaults to ignoring hidden files (starting with a dot) """ root_path = Path(root_path) # assess root root_index, root_files, root_folders = _assess_folder( root_path, suffixes, default_index, ignore_matches ) if not root_index: raise IOError(f"path does not contain a root file: {root_path}") # create root item and child folders root_item, indexed_folders = _doc_item_from_path( root_path, root_path, root_index, root_files, root_folders, suffixes, default_index, ignore_matches, ) # create base site-map site_map = SiteMap(root=root_item, file_format=file_format) # we add all files to the site map, even if they don't have descendants # so we may later change their title for root_file in root_files: site_map[root_file] = Document(root_file) # while there are subfolders add them to the site-map while indexed_folders: ( sub_path, child_index, child_files, child_folders, ) = indexed_folders.pop(0) for child_file in child_files: child_docname = (sub_path / child_file).relative_to(root_path).as_posix() assert child_docname not in site_map site_map[child_docname] = Document(child_docname) doc_item, new_indexed_folders = _doc_item_from_path( root_path, sub_path, child_index, child_files, child_folders, suffixes, default_index, ignore_matches, ) assert doc_item.docname not in site_map site_map[doc_item.docname] = doc_item indexed_folders += new_indexed_folders return site_map def _doc_item_from_path( root: Path, folder: Path, index_docname: str, other_docnames: Sequence[str], folder_names: Sequence[str], suffixes: Sequence[str], default_index: str, ignore_matches: Sequence[str], ): """Return the ``Document`` and children folders that contain an index.""" file_items = [ FileItem((folder / name).relative_to(root).as_posix()) for name in other_docnames ] # get folders with sub-indexes indexed_folders = [] index_items = [] for folder_name in folder_names: sub_folder = folder / folder_name child_index, child_files, child_folders = _assess_folder( sub_folder, suffixes, default_index, ignore_matches ) if not child_index: # TODO handle folders with no files, but files in sub-folders continue indexed_folders.append((sub_folder, child_index, child_files, child_folders)) index_items.append( FileItem((sub_folder / child_index).relative_to(root).as_posix()) ) doc_item = Document( docname=(folder / index_docname).relative_to(root).as_posix(), subtrees=[TocTree(items=file_items + index_items)] # type: ignore[arg-type] if (file_items or index_items) else [], ) return doc_item, indexed_folders def natural_sort(iterable): """Sort an iterable by https://en.wikipedia.org/wiki/Natural_sort_order.""" def _convert(text): return int(text) if text.isdigit() else text.lower() def _alphanum_key(key): return [_convert(c) for c in re.split("([0-9]+)", key)] return sorted(iterable, key=_alphanum_key) def _assess_folder( folder: Path, suffixes: Sequence[str], default_index: str, ignore_matches: Sequence[str], ) -> Tuple[Optional[str], Sequence[str], Sequence[str]]: """Assess the folder for ToC items. Strips suffixes from file names and sorts file/folder names by natural order. :returns: (index file name, other file names, folders) """ if not folder.is_dir(): raise IOError(f"path must be a directory: {folder}") def _strip_suffix(name: str) -> str: for suffix in suffixes: if name.endswith(suffix): return name[: -len(suffix)] return name # conversion to a set is to remove duplicates, e.g. doc.rst and doc.md sub_files = natural_sort( list( set( [ _strip_suffix(path.name) for path in folder.iterdir() if path.is_file() and any(path.name.endswith(suffix) for suffix in suffixes) and (not any(fnmatch(path.name, pat) for pat in ignore_matches)) ] ) ) ) sub_folders = natural_sort( [ path.name for path in folder.iterdir() if path.is_dir() if (not any(fnmatch(path.name, pat) for pat in ignore_matches)) ] ) if not sub_files: return (None, sub_files, sub_folders) # get the index file for this folder try: index = sub_files.index(default_index) except ValueError: index = 0 index_file = sub_files.pop(index) return (index_file, sub_files, sub_folders) def migrate_jupyter_book( toc: Union[Path, Dict[str, Any], list], ) -> Dict[str, Any]: """Migrate a jupyter-book v0.10.2 toc.""" if isinstance(toc, Path): with toc.open(encoding="utf8") as handle: toc = yaml.safe_load(handle) # convert list to dict if isinstance(toc, list): toc_updated = toc[0] if not isinstance(toc_updated, dict): raise MalformedError("First list item is not a dict") if len(toc) > 1: first_items: List[dict] = [] top_items_key = "sections" # this is the default top-level key # The first set of pages will be called *either* sections or chapters if "sections" in toc_updated and "chapters" in toc_updated: raise MalformedError( "First list item contains both 'chapters' and 'sections' keys" ) for key in ("sections", "chapters"): if key in toc_updated: top_items_key = key items = toc_updated.pop(key) if not isinstance(items, Sequence): raise MalformedError(f"First list item '{key}' is not a list") first_items += items # add list items after to same level first_items += toc[1:] # check for part keys (and also chapter which was deprecated) contains_part = any( ("part" in item or "chapter" in item) for item in first_items ) contains_file = any("file" in item for item in first_items) if contains_part and contains_file: raise MalformedError("top-level contains mixed 'part' and 'file' keys") toc_updated["parts" if contains_part else top_items_key] = first_items toc = toc_updated elif not isinstance(toc, dict): raise MalformedError("ToC is not a list or dict") # convert first `file` to `root` if "file" not in toc: raise MalformedError("no top-level 'file' key found") toc["root"] = toc.pop("file") # setting `titlesonly` True is now part of the file format # toc["defaults"] = {"titlesonly": True} # we should now have a dict with either a 'parts', 'chapters', or 'sections' key top_level_keys = {"parts", "chapters", "sections"}.intersection(toc.keys()) if len(top_level_keys) > 1: raise MalformedError( f"There is more than one top-level key: {top_level_keys!r}" ) # from the top-level key we can now derive the file-format (for key-mappings) file_format = { "": "jb-book", "parts": "jb-book", "chapters": "jb-book", "sections": "jb-article", }["" if not top_level_keys else list(top_level_keys)[0]] # change all parts to DEFAULT_SUBTREES_KEY # change all chapters to DEFAULT_ITEMS_KEY # change all part/chapter to caption dicts = [toc] while dicts: dct = dicts.pop(0) if "chapters" in dct and "sections" in dct: raise MalformedError(f"both 'chapters' and 'sections' in same dict: {dct}") if "parts" in dct: dct[DEFAULT_SUBTREES_KEY] = dct.pop("parts") if "sections" in dct: dct[DEFAULT_ITEMS_KEY] = dct.pop("sections") if "chapters" in dct: dct[DEFAULT_ITEMS_KEY] = dct.pop("chapters") for key in ("part", "chapter"): if key in dct: dct["caption"] = dct.pop(key) # add nested dicts for val in dct.values(): for item in val if isinstance(val, Sequence) else [val]: if isinstance(item, dict): dicts.append(item) # if `numbered` at top level, move to options or copy to each subtree if "numbered" in toc: numbered = toc.pop("numbered") if DEFAULT_ITEMS_KEY in toc: toc["options"] = {"numbered": numbered} for subtree in toc.get(DEFAULT_SUBTREES_KEY, []): if "numbered" not in subtree: subtree["numbered"] = numbered # now convert to a site map, so we can validate try: site_map = parse_toc_data(toc) except MalformedError as err: raise MalformedError(f"Error parsing migrated output: {err}") from err # change the file format and convert back to a dict site_map.file_format = file_format return create_toc_dict(site_map, skip_defaults=True) sphinx-external-toc-1.0.1/tests/000077500000000000000000000000001453603135500165745ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/_bad_toc_files/000077500000000000000000000000001453603135500215105ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/_bad_toc_files/bad_option_value.yml000066400000000000000000000000731453603135500255450ustar00rootroot00000000000000root: intro options: titlesonly: 2 entries: - file: doc1 sphinx-external-toc-1.0.1/tests/_bad_toc_files/bad_url.yml000066400000000000000000000000501453603135500236360ustar00rootroot00000000000000root: index entries: - url: example.com sphinx-external-toc-1.0.1/tests/_bad_toc_files/empty.yml000066400000000000000000000000001453603135500233570ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/_bad_toc_files/empty_items.yml000066400000000000000000000000301453603135500245630ustar00rootroot00000000000000root: index entries: [] sphinx-external-toc-1.0.1/tests/_bad_toc_files/empty_subtrees.yml000066400000000000000000000000311453603135500252770ustar00rootroot00000000000000root: index subtrees: [] sphinx-external-toc-1.0.1/tests/_bad_toc_files/file_and_glob_present.yml000066400000000000000000000000561453603135500265400ustar00rootroot00000000000000root: index entries: - file: doc glob: doc* sphinx-external-toc-1.0.1/tests/_bad_toc_files/items_in_glob.yml000066400000000000000000000000751453603135500250470ustar00rootroot00000000000000root: index entries: - glob: doc* entries: - file: other sphinx-external-toc-1.0.1/tests/_bad_toc_files/items_in_url.yml000066400000000000000000000001121453603135500247160ustar00rootroot00000000000000root: index entries: - url: http://example.com entries: - file: other sphinx-external-toc-1.0.1/tests/_bad_toc_files/list.yml000066400000000000000000000000101453603135500231750ustar00rootroot00000000000000- 1 - 2 sphinx-external-toc-1.0.1/tests/_bad_toc_files/no_root.yml000066400000000000000000000000051453603135500237050ustar00rootroot00000000000000x: a sphinx-external-toc-1.0.1/tests/_bad_toc_files/subtree_with_no_items.yml000066400000000000000000000000531453603135500266320ustar00rootroot00000000000000root: index subtrees: - caption: something sphinx-external-toc-1.0.1/tests/_bad_toc_files/unknown_keys.yml000066400000000000000000000000251453603135500247620ustar00rootroot00000000000000root: main x: a y: b sphinx-external-toc-1.0.1/tests/_bad_toc_files/unknown_keys_nested.yml000066400000000000000000000001321453603135500263230ustar00rootroot00000000000000root: main subtrees: - file: doc1 entries: - file: doc2 - file: doc3 unknown: 1 sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/000077500000000000000000000000001453603135500230655ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/chapters.yml000066400000000000000000000010271453603135500254210ustar00rootroot00000000000000- file: index title: Toc - chapter: A section sections: - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage1 title: Asubpage - chapter: Another section sections: - file: content1 title: Content1 sections: - file: subfolder/asubpage2 title: Asubpage - file: content2 title: Content2 sections: - file: subfolder/asubpage3 title: Asubpage - file: content3 title: Content3 sections: - file: subfolder/asubpage4 title: Asubpage sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/jb_docs_toc.yml000066400000000000000000000036251453603135500260660ustar00rootroot00000000000000- file: intro - part: Tutorials chapters: - file: start/your-first-book sections: - file: start/overview - file: start/create - file: start/build - file: start/publish - file: tutorials/references - file: basics/questions - part: Create your book chapters: - file: customize/toc - file: basics/create - file: content/index sections: - file: content/myst - file: content/content-blocks - file: content/references - file: content/layout - file: content/citations - file: content/math - file: content/figures - file: content/metadata - file: interactive/hiding - file: file-types/index sections: - file: file-types/markdown - file: file-types/notebooks - file: file-types/myst-notebooks - file: file-types/jupytext - file: file-types/restructuredtext - part: Build your book chapters: - file: basics/build - file: execute/index sections: - file: content/code-outputs - file: content/execute - file: interactive/interactive - file: basics/page - part: Publish your book chapters: - file: publish/web sections: - file: publish/gh-pages - file: publish/netlify - file: basics/repository - file: interactive/launchbuttons - file: interactive/comments sections: - file: interactive/comments/hypothesis - file: interactive/comments/utterances - file: advanced/html - file: advanced/pdf - part: Advanced usage chapters: - file: advanced/sphinx - file: advanced/windows - file: explain/sphinx - part: Reference chapters: - file: customize/config - file: reference/cheatsheet - file: reference/cli - file: reference/glossary - part: About Jupyter Book chapters: - url: https://executablebooks.org/en/latest/gallery.html title: Gallery of Jupyter Books - file: contribute/intro - file: cite - file: reference/_changelog title: Change log sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/list_and_nested.yml000066400000000000000000000004061453603135500267470ustar00rootroot00000000000000- file: index title: Toc sections: - file: content1 title: Content1 - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage - file: content2 title: Content2 - file: content3 title: Content3 sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/numbered_chapters.yml000066400000000000000000000004271453603135500273050ustar00rootroot00000000000000- file: index title: Toc numbered: true chapters: - file: content1 title: Content1 - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage - file: content2 title: Content2 - file: content3 title: Content3 sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/numbered_depth.yml000066400000000000000000000004241453603135500265750ustar00rootroot00000000000000- file: index title: Toc numbered: 1 chapters: - file: content1 title: Content1 - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage - file: content2 title: Content2 - file: content3 title: Content3 sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/numbered_depth_parts_subset.yml000066400000000000000000000005731453603135500314000ustar00rootroot00000000000000- file: index title: Toc - part: Chapter 1 numbered: true chapters: - file: content1 title: Content1 - part: Chapter 2 chapters: - file: content2 title: Content2 - file: content3 title: Content3 - part: Chapter 3 numbered: 1 chapters: - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/numbered_parts.yml000066400000000000000000000005171453603135500266250ustar00rootroot00000000000000- file: index title: Toc numbered: true - part: Chapter 1 chapters: - file: content1 title: Content1 - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage - part: Chapter 2 chapters: - file: content2 title: Content2 - file: content3 title: Content3 sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/numbered_parts_subset.yml000066400000000000000000000005761453603135500302170ustar00rootroot00000000000000- file: index title: Toc - part: Chapter 1 numbered: true chapters: - file: content1 title: Content1 - part: Chapter 2 chapters: - file: content2 title: Content2 - file: content3 title: Content3 - part: Chapter 3 numbered: true chapters: - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/parts.yml000066400000000000000000000010211453603135500247330ustar00rootroot00000000000000- file: index title: Toc - part: A section chapters: - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage1 title: Asubpage - part: Another section chapters: - file: content1 title: Content1 sections: - file: subfolder/asubpage2 title: Asubpage - file: content2 title: Content2 sections: - file: subfolder/asubpage3 title: Asubpage - file: content3 title: Content3 sections: - file: subfolder/asubpage4 title: Asubpage sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/simple_dict.yml000066400000000000000000000003621453603135500261050ustar00rootroot00000000000000file: index title: Toc sections: - file: content1 title: Content1 - file: content2 title: Content2 - file: content3 title: Content3 - file: subfolder/index title: Subfolder sections: - file: subfolder/asubpage title: Asubpage sphinx-external-toc-1.0.1/tests/_jb_migrate_toc_files/simple_list.yml000066400000000000000000000000601453603135500261300ustar00rootroot00000000000000- file: index - file: content1 - file: content2 sphinx-external-toc-1.0.1/tests/_toc_files/000077500000000000000000000000001453603135500207025ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/_toc_files/basic.yml000066400000000000000000000004321453603135500225050ustar00rootroot00000000000000defaults: titlesonly: true root: intro subtrees: - caption: Part Caption numbered: true entries: - file: doc1 - file: doc2 - file: doc3 subtrees: - entries: - file: subfolder/doc4 - url: https://example.com meta: regress: intro sphinx-external-toc-1.0.1/tests/_toc_files/basic_compressed.yml000066400000000000000000000003011453603135500247240ustar00rootroot00000000000000defaults: titlesonly: true options: numbered: true root: intro entries: - file: doc1 - file: doc2 - file: doc3 entries: - file: doc4 - url: https://example.com meta: regress: intro sphinx-external-toc-1.0.1/tests/_toc_files/exclude_missing.yml000066400000000000000000000002071453603135500246060ustar00rootroot00000000000000root: intro entries: - file: doc1 - glob: subfolder/other* meta: create_files: - doc2 - subfolder/other1 exclude_missing: true sphinx-external-toc-1.0.1/tests/_toc_files/glob.yml000066400000000000000000000001231453603135500223440ustar00rootroot00000000000000root: intro entries: - glob: doc* meta: create_files: - doc1 - doc2 - doc3 sphinx-external-toc-1.0.1/tests/_toc_files/nested.yml000066400000000000000000000003141453603135500227050ustar00rootroot00000000000000root: intro entries: - file: folder/doc1 - file: folder/doc2 - file: folder/doc3 entries: - file: folder/subfolder/doc4 - glob: folder/globfolder/* meta: create_files: - folder/globfolder/glob1 sphinx-external-toc-1.0.1/tests/_toc_files/tableofcontents.yml000066400000000000000000000002321453603135500246140ustar00rootroot00000000000000root: intro subtrees: - entries: - file: doc1 - entries: - file: doc2 meta: create_append: intro: | .. tableofcontents:: regress: intro sphinx-external-toc-1.0.1/tests/_warning_toc_files/000077500000000000000000000000001453603135500224275ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/_warning_toc_files/contains_toctree.yml000066400000000000000000000002361453603135500265160ustar00rootroot00000000000000root: intro meta: create_files: - doc1 create_append: intro: | .. toctree:: doc1 expected_warning: toctree directive not expected sphinx-external-toc-1.0.1/tests/_warning_toc_files/file_not_in_toc.yml000066400000000000000000000001461453603135500263050ustar00rootroot00000000000000root: intro meta: create_files: - doc1 expected_warning: document isn't included in any toctree sphinx-external-toc-1.0.1/tests/_warning_toc_files/multiple_tableofcontents.yml000066400000000000000000000002711453603135500302570ustar00rootroot00000000000000root: intro entries: - file: doc1 meta: create_append: intro: | .. tableofcontents:: .. tableofcontents:: expected_warning: more than one tableofcontents directive sphinx-external-toc-1.0.1/tests/_warning_toc_files/no_glob_match.yml000066400000000000000000000001211453603135500257370ustar00rootroot00000000000000root: intro entries: - glob: doc* meta: expected_warning: toctree glob pattern sphinx-external-toc-1.0.1/tests/_warning_toc_files/tableofcontents_no_toc.yml000066400000000000000000000002311453603135500277010ustar00rootroot00000000000000root: intro meta: create_append: intro: | .. tableofcontents:: expected_warning: tableofcontents directive in document with no descendants sphinx-external-toc-1.0.1/tests/conftest.py000066400000000000000000000016251453603135500207770ustar00rootroot00000000000000import re import pytest pytest_plugins = "sphinx.testing.fixtures" # comparison files will need updating # alternatively the resolution of https://github.com/ESSS/pytest-regressions/issues/32 @pytest.fixture() def file_regression(file_regression): return FileRegression(file_regression) class FileRegression: ignores = ( # TODO: Remove when support for Sphinx<=6 is dropped, re.escape(" translation_progress=\"{'total': 0, 'translated': 0}\""), # TODO: Remove when support for Sphinx<7.2 is dropped, r"original_uri=\"[^\"]*\"\s", ) def __init__(self, file_regression): self.file_regression = file_regression def check(self, data, **kwargs): return self.file_regression.check(self._strip_ignores(data), **kwargs) def _strip_ignores(self, data): for ig in self.ignores: data = re.sub(ig, "", data) return data sphinx-external-toc-1.0.1/tests/test_api.py000066400000000000000000000033151453603135500207600ustar00rootroot00000000000000from sphinx_external_toc.api import Document, FileItem, SiteMap, TocTree def test_sitemap_get_changed_identical(): """Test for identical sitemaps.""" root1 = Document("root") root1.subtrees = [TocTree([])] sitemap1 = SiteMap(root1) root2 = Document("root") root2.subtrees = [TocTree([])] sitemap2 = SiteMap(root2) assert sitemap1.get_changed(sitemap2) == set() def test_sitemap_get_changed_root(): """Test for sitemaps with changed root.""" doc1 = Document("doc1") doc2 = Document("doc2") sitemap1 = SiteMap(doc1) sitemap1["doc2"] = doc2 sitemap2 = SiteMap(doc2) sitemap1["doc1"] = doc1 assert sitemap1.get_changed(sitemap2) == {"doc1"} def test_sitemap_get_changed_title(): """Test for sitemaps with changed title.""" root1 = Document("root") root1.title = "a" sitemap1 = SiteMap(root1) root2 = Document("root") root2.title = "b" sitemap2 = SiteMap(root2) assert sitemap1.get_changed(sitemap2) == {"root"} def test_sitemap_get_changed_subtrees(): """Test for sitemaps with changed subtrees.""" root1 = Document("root") root1.subtrees = [TocTree([])] sitemap1 = SiteMap(root1) root2 = Document("root") root2.subtrees = [TocTree([FileItem("a")])] sitemap2 = SiteMap(root2) assert sitemap1.get_changed(sitemap2) == {"root"} def test_sitemap_get_changed_subtrees_numbered(): """Test for sitemaps with changed numbered option.""" root1 = Document("root") root1.subtrees = [TocTree([], numbered=False)] sitemap1 = SiteMap(root1) root2 = Document("root") root2.subtrees = [TocTree([], numbered=True)] sitemap2 = SiteMap(root2) assert sitemap1.get_changed(sitemap2) == {"root"} sphinx-external-toc-1.0.1/tests/test_cli.py000066400000000000000000000036671453603135500207700ustar00rootroot00000000000000import os from pathlib import Path from typing import List import pytest from click.testing import CliRunner from sphinx_external_toc import __version__ from sphinx_external_toc.cli import create_toc, main, migrate_toc, parse_toc @pytest.fixture() def invoke_cli(): """Run CLI and do standard checks.""" def _func(command, args: List[str], assert_exit: bool = True): runner = CliRunner() result = runner.invoke(command, args) if assert_exit: assert result.exit_code == 0, result.output return result yield _func def test_version(invoke_cli): result = invoke_cli(main, ["--version"]) assert __version__ in result.output def test_parse_toc(invoke_cli): path = os.path.abspath(Path(__file__).parent.joinpath("_toc_files", "basic.yml")) result = invoke_cli(parse_toc, [path]) assert "intro" in result.output def test_create_toc(tmp_path, invoke_cli, file_regression): # create project files files = [ "index.rst", "1_a_title.rst", "11_another_title.rst", ".hidden_file.rst", ".hidden_folder/index.rst", "1_a_subfolder/index.rst", "2_another_subfolder/index.rst", "2_another_subfolder/other.rst", "3_subfolder/1_no_index.rst", "3_subfolder/2_no_index.rst", "14_subfolder/index.rst", "14_subfolder/subsubfolder/index.rst", "14_subfolder/subsubfolder/other.rst", ] for posix in files: path = tmp_path.joinpath(*posix.split("/")) path.parent.mkdir(parents=True, exist_ok=True) path.touch() result = invoke_cli(create_toc, [os.path.abspath(tmp_path), "-t"]) file_regression.check(result.output.rstrip()) def test_migrate_toc(invoke_cli): path = os.path.abspath( Path(__file__).parent.joinpath("_jb_migrate_toc_files", "simple_list.yml") ) result = invoke_cli(migrate_toc, [path]) assert "root: index" in result.output sphinx-external-toc-1.0.1/tests/test_cli/000077500000000000000000000000001453603135500204025ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/test_cli/test_create_toc.txt000066400000000000000000000011151453603135500243100ustar00rootroot00000000000000root: index entries: - file: 1_a_title title: A title - file: 11_another_title title: Another title - file: 1_a_subfolder/index title: A subfolder - file: 2_another_subfolder/index title: Another subfolder entries: - file: 2_another_subfolder/other title: Other - file: 3_subfolder/1_no_index title: No index entries: - file: 3_subfolder/2_no_index title: No index - file: 14_subfolder/index title: Subfolder entries: - file: 14_subfolder/subsubfolder/index title: Subsubfolder entries: - file: 14_subfolder/subsubfolder/other title: Othersphinx-external-toc-1.0.1/tests/test_parsing.py000066400000000000000000000040361453603135500216530ustar00rootroot00000000000000from pathlib import Path import pytest from sphinx_external_toc.parsing import MalformedError, create_toc_dict, parse_toc_yaml TOC_FILES = list(Path(__file__).parent.joinpath("_toc_files").glob("*.yml")) @pytest.mark.parametrize( "path", TOC_FILES, ids=[path.name.rsplit(".", 1)[0] for path in TOC_FILES] ) def test_file_to_sitemap(path: Path, data_regression): site_map = parse_toc_yaml(path) data_regression.check(site_map.as_json()) @pytest.mark.parametrize( "path", TOC_FILES, ids=[path.name.rsplit(".", 1)[0] for path in TOC_FILES] ) def test_create_toc_dict(path: Path, data_regression): site_map = parse_toc_yaml(path) data = create_toc_dict(site_map) data_regression.check(data) TOC_FILES_BAD = list(Path(__file__).parent.joinpath("_bad_toc_files").glob("*.yml")) ERROR_MESSAGES = { "bad_option_value.yml": "toctree validation @ '/': 'titlesonly'", "bad_url.yml": "entry validation @ '/entries/0': 'url' must match regex", "empty.yml": "toc is not a mapping:", "file_and_glob_present.yml": "entry contains incompatible keys .* @ '/entries/0'", "list.yml": "toc is not a mapping:", "unknown_keys.yml": "Unknown keys found: .* @ '/'", "empty_items.yml": "'entries' not a non-empty list @ '/'", "items_in_glob.yml": "entry contains incompatible keys 'glob' and 'entries' @ '/entries/0'", "no_root.yml": "'root' key not found @ '/'", "unknown_keys_nested.yml": ( "Unknown keys found: {'unknown'}, allow.* " "@ '/subtrees/0/entries/1/'" ), "empty_subtrees.yml": "'subtrees' not a non-empty list @ '/'", "items_in_url.yml": "entry contains incompatible keys 'url' and 'entries' @ '/entries/0'", "subtree_with_no_items.yml": "entry not a mapping containing 'entries' key @ '/subtrees/0/'", } @pytest.mark.parametrize( "path", TOC_FILES_BAD, ids=[path.name.rsplit(".", 1)[0] for path in TOC_FILES_BAD] ) def test_malformed_file_parse(path: Path): message = ERROR_MESSAGES[path.name] with pytest.raises(MalformedError, match=message): parse_toc_yaml(path) sphinx-external-toc-1.0.1/tests/test_parsing/000077500000000000000000000000001453603135500212765ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/test_parsing/test_create_toc_dict_basic_.yml000066400000000000000000000003711453603135500274740ustar00rootroot00000000000000entries: - file: doc1 - file: doc2 - entries: - file: subfolder/doc4 - url: https://example.com file: doc3 options: titlesonly: true meta: regress: intro options: caption: Part Caption numbered: true titlesonly: true root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_create_toc_dict_basic_compressed_.yml000066400000000000000000000003271453603135500317210ustar00rootroot00000000000000entries: - file: doc1 - file: doc2 - entries: - file: doc4 - url: https://example.com file: doc3 options: titlesonly: true meta: regress: intro options: numbered: true titlesonly: true root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_create_toc_dict_exclude_missing_.yml000066400000000000000000000002071453603135500315730ustar00rootroot00000000000000entries: - file: doc1 - glob: subfolder/other* meta: create_files: - doc2 - subfolder/other1 exclude_missing: true root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_create_toc_dict_glob_.yml000066400000000000000000000001231453603135500273310ustar00rootroot00000000000000entries: - glob: doc* meta: create_files: - doc1 - doc2 - doc3 root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_create_toc_dict_nested_.yml000066400000000000000000000003141453603135500276720ustar00rootroot00000000000000entries: - file: folder/doc1 - file: folder/doc2 - entries: - file: folder/subfolder/doc4 - glob: folder/globfolder/* file: folder/doc3 meta: create_files: - folder/globfolder/glob1 root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_create_toc_dict_tableofcontents_.yml000066400000000000000000000002341453603135500316030ustar00rootroot00000000000000meta: create_append: intro: '.. tableofcontents:: ' regress: intro root: intro subtrees: - entries: - file: doc1 - entries: - file: doc2 sphinx-external-toc-1.0.1/tests/test_parsing/test_file_to_sitemap_basic_.yml000066400000000000000000000013631453603135500275260ustar00rootroot00000000000000documents: doc1: docname: doc1 subtrees: [] title: null doc2: docname: doc2 subtrees: [] title: null doc3: docname: doc3 subtrees: - caption: null hidden: true items: - subfolder/doc4 - title: null url: https://example.com maxdepth: -1 numbered: false reversed: false titlesonly: true title: null intro: docname: intro subtrees: - caption: Part Caption hidden: true items: - doc1 - doc2 - doc3 maxdepth: -1 numbered: true reversed: false titlesonly: true title: null subfolder/doc4: docname: subfolder/doc4 subtrees: [] title: null meta: regress: intro root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_file_to_sitemap_basic_compressed_.yml000066400000000000000000000013151453603135500317470ustar00rootroot00000000000000documents: doc1: docname: doc1 subtrees: [] title: null doc2: docname: doc2 subtrees: [] title: null doc3: docname: doc3 subtrees: - caption: null hidden: true items: - doc4 - title: null url: https://example.com maxdepth: -1 numbered: false reversed: false titlesonly: true title: null doc4: docname: doc4 subtrees: [] title: null intro: docname: intro subtrees: - caption: null hidden: true items: - doc1 - doc2 - doc3 maxdepth: -1 numbered: true reversed: false titlesonly: true title: null meta: regress: intro root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_file_to_sitemap_exclude_missing_.yml000066400000000000000000000006111453603135500316220ustar00rootroot00000000000000documents: doc1: docname: doc1 subtrees: [] title: null intro: docname: intro subtrees: - caption: null hidden: true items: - doc1 - subfolder/other* maxdepth: -1 numbered: false reversed: false titlesonly: false title: null meta: create_files: - doc2 - subfolder/other1 exclude_missing: true root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_file_to_sitemap_glob_.yml000066400000000000000000000004321453603135500273640ustar00rootroot00000000000000documents: intro: docname: intro subtrees: - caption: null hidden: true items: - doc* maxdepth: -1 numbered: false reversed: false titlesonly: false title: null meta: create_files: - doc1 - doc2 - doc3 root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_file_to_sitemap_nested_.yml000066400000000000000000000015041453603135500277240ustar00rootroot00000000000000documents: folder/doc1: docname: folder/doc1 subtrees: [] title: null folder/doc2: docname: folder/doc2 subtrees: [] title: null folder/doc3: docname: folder/doc3 subtrees: - caption: null hidden: true items: - folder/subfolder/doc4 - folder/globfolder/* maxdepth: -1 numbered: false reversed: false titlesonly: false title: null folder/subfolder/doc4: docname: folder/subfolder/doc4 subtrees: [] title: null intro: docname: intro subtrees: - caption: null hidden: true items: - folder/doc1 - folder/doc2 - folder/doc3 maxdepth: -1 numbered: false reversed: false titlesonly: false title: null meta: create_files: - folder/globfolder/glob1 root: intro sphinx-external-toc-1.0.1/tests/test_parsing/test_file_to_sitemap_tableofcontents_.yml000066400000000000000000000011111453603135500316260ustar00rootroot00000000000000documents: doc1: docname: doc1 subtrees: [] title: null doc2: docname: doc2 subtrees: [] title: null intro: docname: intro subtrees: - caption: null hidden: true items: - doc1 maxdepth: -1 numbered: false reversed: false titlesonly: false - caption: null hidden: true items: - doc2 maxdepth: -1 numbered: false reversed: false titlesonly: false title: null meta: create_append: intro: '.. tableofcontents:: ' regress: intro root: intro sphinx-external-toc-1.0.1/tests/test_sphinx.py000066400000000000000000000123471453603135500215250ustar00rootroot00000000000000import os from pathlib import Path import pytest from sphinx import version_info as sphinx_version_info from sphinx.testing.util import SphinxTestApp from sphinx_external_toc.tools import create_site_from_toc TOC_FILES = list(Path(__file__).parent.joinpath("_toc_files").glob("*.yml")) TOC_FILES_WARN = list( Path(__file__).parent.joinpath("_warning_toc_files").glob("*.yml") ) CONF_CONTENT = """ extensions = ["sphinx_external_toc"] external_toc_path = "_toc.yml" """ class SphinxBuild: def __init__(self, app: SphinxTestApp, src: Path): self.app = app self.src = src def build(self, assert_pass=True): self.app.build() if assert_pass: assert self.warnings == "", self.status return self @property def status(self): return self.app._status.getvalue() @property def warnings(self): return self.app._warning.getvalue() @property def outdir(self): return Path(self.app.outdir) @pytest.fixture() def sphinx_build_factory(make_app): def _func(src_path: Path, **kwargs) -> SphinxBuild: # For compatibility with multiple versions of sphinx, convert pathlib.Path to # sphinx.testing.path.path here. if sphinx_version_info >= (7, 2): app_srcdir = src_path else: from sphinx.testing.path import path app_srcdir = path(os.fspath(src_path)) app = make_app(srcdir=app_srcdir, **kwargs) return SphinxBuild(app, src_path) yield _func @pytest.mark.parametrize( "path", TOC_FILES, ids=[path.name.rsplit(".", 1)[0] for path in TOC_FILES] ) def test_success(path: Path, tmp_path: Path, sphinx_build_factory, file_regression): """Test successful builds.""" src_dir = tmp_path / "srcdir" # write document files site_map = create_site_from_toc(path, root_path=src_dir) # write conf.py src_dir.joinpath("conf.py").write_text( CONF_CONTENT + ( "external_toc_exclude_missing = True" if site_map.meta.get("exclude_missing") is True else "" ), encoding="utf8", ) # run sphinx builder = sphinx_build_factory(src_dir) builder.build() # optionally check the doctree of a file if "regress" in site_map.meta: doctree = builder.app.env.get_doctree(site_map.meta["regress"]) doctree["source"] = site_map.meta["regress"] file_regression.check(doctree.pformat(), extension=".xml", encoding="utf8") def test_gettext(tmp_path: Path, sphinx_build_factory): """Test the gettext builder runs correctly.""" src_dir = tmp_path / "srcdir" # write document files toc_path = Path(__file__).parent.joinpath("_toc_files", "basic.yml") create_site_from_toc(toc_path, root_path=src_dir, toc_name=None) # write conf.py content = f""" extensions = ["sphinx_external_toc"] external_toc_path = {Path(os.path.abspath(toc_path)).as_posix()!r} """ src_dir.joinpath("conf.py").write_text(content, encoding="utf8") # run sphinx builder = sphinx_build_factory(src_dir, buildername="gettext") builder.build() @pytest.mark.parametrize( "path", TOC_FILES_WARN, ids=[path.name.rsplit(".", 1)[0] for path in TOC_FILES_WARN] ) def test_warning(path: Path, tmp_path: Path, sphinx_build_factory): src_dir = tmp_path / "srcdir" # write document files sitemap = create_site_from_toc(path, root_path=src_dir) # write conf.py src_dir.joinpath("conf.py").write_text(CONF_CONTENT, encoding="utf8") # run sphinx builder = sphinx_build_factory(src_dir) builder.build(assert_pass=False) assert sitemap.meta["expected_warning"] in builder.warnings def test_absolute_path(tmp_path: Path, sphinx_build_factory): """Test if `external_toc_path` is supplied as an absolute path.""" src_dir = tmp_path / "srcdir" # write document files toc_path = Path(__file__).parent.joinpath("_toc_files", "basic.yml") create_site_from_toc(toc_path, root_path=src_dir, toc_name=None) # write conf.py content = f""" extensions = ["sphinx_external_toc"] external_toc_path = {Path(os.path.abspath(toc_path)).as_posix()!r} """ src_dir.joinpath("conf.py").write_text(content, encoding="utf8") # run sphinx builder = sphinx_build_factory(src_dir) builder.build() def test_file_extensions(tmp_path: Path, sphinx_build_factory): """Test for tocs containing docnames with file extensions.""" src_dir = tmp_path / "srcdir" src_dir.mkdir(exist_ok=True) # write documents src_dir.joinpath("intro.rst").write_text("Head\n====\n", encoding="utf8") src_dir.joinpath("markdown.rst").write_text("Head\n====\n", encoding="utf8") src_dir.joinpath("notebooks.rst").write_text("Head\n====\n", encoding="utf8") # write toc toc_path = tmp_path / "toc.yml" toc_path.write_text( """ format: jb-book root: intro.rst chapters: - file: markdown.rst sections: - file: notebooks.rst """, encoding="utf8", ) # write conf.py content = f""" extensions = ["sphinx_external_toc"] external_toc_path = {Path(os.path.abspath(toc_path)).as_posix()!r} """ src_dir.joinpath("conf.py").write_text(content, encoding="utf8") # run sphinx builder = sphinx_build_factory(src_dir) builder.build() sphinx-external-toc-1.0.1/tests/test_sphinx/000077500000000000000000000000001453603135500211445ustar00rootroot00000000000000sphinx-external-toc-1.0.1/tests/test_sphinx/test_success_basic_.xml000066400000000000000000000007061453603135500257000ustar00rootroot00000000000000
Heading: intro.rst <compound classes="toctree-wrapper"> <toctree caption="Part Caption" entries="(None,\ 'doc1') (None,\ 'doc2') (None,\ 'doc3')" glob="False" hidden="True" includefiles="doc1 doc2 doc3" includehidden="False" maxdepth="-1" numbered="999" parent="intro" rawcaption="Part Caption" titlesonly="True"> ����������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_sphinx/test_success_basic_compressed_.xml����������������������0000664�0000000�0000000�00000000662�14536031355�0030125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<document source="intro"> <section ids="heading-intro-rst" names="heading:\ intro.rst"> <title> Heading: intro.rst <compound classes="toctree-wrapper"> <toctree caption="True" entries="(None,\ 'doc1') (None,\ 'doc2') (None,\ 'doc3')" glob="False" hidden="True" includefiles="doc1 doc2 doc3" includehidden="False" maxdepth="-1" numbered="999" parent="intro" rawcaption="" titlesonly="True"> ������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_sphinx/test_success_tableofcontents_.xml�����������������������0000664�0000000�0000000�00000001205�14536031355�0030004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<document source="intro"> <section ids="heading-intro-rst" names="heading:\ intro.rst"> <title> Heading: intro.rst <compound classes="toctree-wrapper"> <toctree caption="True" entries="(None,\ 'doc1')" glob="False" hidden="False" includefiles="doc1" includehidden="False" maxdepth="-1" numbered="0" parent="intro" rawcaption="" titlesonly="False"> <compound classes="toctree-wrapper"> <toctree caption="True" entries="(None,\ 'doc2')" glob="False" hidden="False" includefiles="doc2" includehidden="False" maxdepth="-1" numbered="0" parent="intro" rawcaption="" titlesonly="False"> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools.py�������������������������������������������������������0000664�0000000�0000000�00000003641�14536031355�0021351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from pathlib import Path import pytest from sphinx_external_toc.parsing import parse_toc_data from sphinx_external_toc.tools import ( create_site_from_toc, create_site_map_from_path, migrate_jupyter_book, ) TOC_FILES = list(Path(__file__).parent.joinpath("_toc_files").glob("*.yml")) JB_TOC_FILES = list( Path(__file__).parent.joinpath("_jb_migrate_toc_files").glob("*.yml") ) @pytest.mark.parametrize( "path", TOC_FILES, ids=[path.name.rsplit(".", 1)[0] for path in TOC_FILES] ) def test_file_to_sitemap(path: Path, tmp_path: Path, data_regression): site_path = tmp_path.joinpath("site") create_site_from_toc(path, root_path=site_path) file_list = [p.relative_to(site_path).as_posix() for p in site_path.glob("**/*")] data_regression.check(sorted(file_list)) def test_create_site_map_from_path(tmp_path: Path, data_regression): # create project files files = [ "index.rst", "1_other.rst", "11_other.rst", ".hidden_file.rst", ".hidden_folder/index.rst", "subfolder1/index.rst", "subfolder2/index.rst", "subfolder2/other.rst", "subfolder3/no_index1.rst", "subfolder3/no_index2.rst", "subfolder14/index.rst", "subfolder14/subsubfolder/index.rst", "subfolder14/subsubfolder/other.rst", ] for posix in files: path = tmp_path.joinpath(*posix.split("/")) path.parent.mkdir(parents=True, exist_ok=True) path.touch() site_map = create_site_map_from_path(tmp_path) data_regression.check(site_map.as_json()) # data = create_toc_dict(site_map) # data_regression.check(data) @pytest.mark.parametrize( "path", JB_TOC_FILES, ids=[path.name.rsplit(".", 1)[0] for path in JB_TOC_FILES] ) def test_migrate_jb(path, data_regression): toc = migrate_jupyter_book(Path(path)) data_regression.check(toc) # check it is a valid toc parse_toc_data(toc) �����������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/���������������������������������������������������������0000775�0000000�0000000�00000000000�14536031355�0020773�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_create_site_map_from_path.yml�����������������������0000664�0000000�0000000�00000003571�14536031355�0027746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������documents: 11_other: docname: 11_other subtrees: [] title: null 1_other: docname: 1_other subtrees: [] title: null index: docname: index subtrees: - caption: null hidden: true items: - 1_other - 11_other - subfolder1/index - subfolder2/index - subfolder3/no_index1 - subfolder14/index maxdepth: -1 numbered: false reversed: false titlesonly: false title: null subfolder1/index: docname: subfolder1/index subtrees: [] title: null subfolder14/index: docname: subfolder14/index subtrees: - caption: null hidden: true items: - subfolder14/subsubfolder/index maxdepth: -1 numbered: false reversed: false titlesonly: false title: null subfolder14/subsubfolder/index: docname: subfolder14/subsubfolder/index subtrees: - caption: null hidden: true items: - subfolder14/subsubfolder/other maxdepth: -1 numbered: false reversed: false titlesonly: false title: null subfolder14/subsubfolder/other: docname: subfolder14/subsubfolder/other subtrees: [] title: null subfolder2/index: docname: subfolder2/index subtrees: - caption: null hidden: true items: - subfolder2/other maxdepth: -1 numbered: false reversed: false titlesonly: false title: null subfolder2/other: docname: subfolder2/other subtrees: [] title: null subfolder3/no_index1: docname: subfolder3/no_index1 subtrees: - caption: null hidden: true items: - subfolder3/no_index2 maxdepth: -1 numbered: false reversed: false titlesonly: false title: null subfolder3/no_index2: docname: subfolder3/no_index2 subtrees: [] title: null meta: {} root: index ���������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_file_to_sitemap_basic_.yml��������������������������0000664�0000000�0000000�00000000131�14536031355�0027213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- _toc.yml - doc1.rst - doc2.rst - doc3.rst - intro.rst - subfolder - subfolder/doc4.rst ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_file_to_sitemap_basic_compressed_.yml���������������0000664�0000000�0000000�00000000103�14536031355�0031436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- _toc.yml - doc1.rst - doc2.rst - doc3.rst - doc4.rst - intro.rst �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_file_to_sitemap_exclude_missing_.yml����������������0000664�0000000�0000000�00000000120�14536031355�0031312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- _toc.yml - doc1.rst - doc2.rst - intro.rst - subfolder - subfolder/other1.rst ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_file_to_sitemap_glob_.yml���������������������������0000664�0000000�0000000�00000000070�14536031355�0027057�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- _toc.yml - doc1.rst - doc2.rst - doc3.rst - intro.rst ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_file_to_sitemap_nested_.yml�������������������������0000664�0000000�0000000�00000000267�14536031355�0027426�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- _toc.yml - folder - folder/doc1.rst - folder/doc2.rst - folder/doc3.rst - folder/globfolder - folder/globfolder/glob1.rst - folder/subfolder - folder/subfolder/doc4.rst - intro.rst �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_file_to_sitemap_tableofcontents_.yml����������������0000664�0000000�0000000�00000000055�14536031355�0031331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- _toc.yml - doc1.rst - doc2.rst - intro.rst �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_chapters_.yml����������������������������0000664�0000000�0000000�00000001052�14536031355�0026706�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-book parts: - caption: A section chapters: - file: subfolder/index sections: - file: subfolder/asubpage1 title: Asubpage title: Subfolder - caption: Another section chapters: - file: content1 sections: - file: subfolder/asubpage2 title: Asubpage title: Content1 - file: content2 sections: - file: subfolder/asubpage3 title: Asubpage title: Content2 - file: content3 sections: - file: subfolder/asubpage4 title: Asubpage title: Content3 root: index title: Toc ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_jb_docs_toc_.yml�������������������������0000664�0000000�0000000�00000003655�14536031355�0027360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-book parts: - caption: Tutorials chapters: - file: start/your-first-book sections: - file: start/overview - file: start/create - file: start/build - file: start/publish - file: tutorials/references - file: basics/questions - caption: Create your book chapters: - file: customize/toc - file: basics/create - file: content/index sections: - file: content/myst - file: content/content-blocks - file: content/references - file: content/layout - file: content/citations - file: content/math - file: content/figures - file: content/metadata - file: interactive/hiding - file: file-types/index sections: - file: file-types/markdown - file: file-types/notebooks - file: file-types/myst-notebooks - file: file-types/jupytext - file: file-types/restructuredtext - caption: Build your book chapters: - file: basics/build - file: execute/index sections: - file: content/code-outputs - file: content/execute - file: interactive/interactive - file: basics/page - caption: Publish your book chapters: - file: publish/web sections: - file: publish/gh-pages - file: publish/netlify - file: basics/repository - file: interactive/launchbuttons - file: interactive/comments sections: - file: interactive/comments/hypothesis - file: interactive/comments/utterances - file: advanced/html - file: advanced/pdf - caption: Advanced usage chapters: - file: advanced/sphinx - file: advanced/windows - file: explain/sphinx - caption: Reference chapters: - file: customize/config - file: reference/cheatsheet - file: reference/cli - file: reference/glossary - caption: About Jupyter Book chapters: - title: Gallery of Jupyter Books url: https://executablebooks.org/en/latest/gallery.html - file: contribute/intro - file: cite - file: reference/_changelog title: Change log root: intro �����������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_list_and_nested_.yml���������������������0000664�0000000�0000000�00000000405�14536031355�0030235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-article root: index sections: - file: content1 title: Content1 - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder - file: content2 title: Content2 - file: content3 title: Content3 title: Toc �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_numbered_chapters_.yml�������������������0000664�0000000�0000000�00000000434�14536031355�0030572�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chapters: - file: content1 title: Content1 - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder - file: content2 title: Content2 - file: content3 title: Content3 format: jb-book options: numbered: true root: index title: Toc ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_numbered_depth_.yml����������������������0000664�0000000�0000000�00000000431�14536031355�0030062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������chapters: - file: content1 title: Content1 - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder - file: content2 title: Content2 - file: content3 title: Content3 format: jb-book options: numbered: 1 root: index title: Toc ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_numbered_depth_parts_subset_.yml���������0000664�0000000�0000000�00000000627�14536031355�0032667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-book parts: - caption: Chapter 1 chapters: - file: content1 title: Content1 numbered: true - caption: Chapter 2 chapters: - file: content2 title: Content2 - file: content3 title: Content3 - caption: Chapter 3 chapters: - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder numbered: 1 root: index title: Toc ���������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_numbered_parts_.yml����������������������0000664�0000000�0000000�00000000571�14536031355�0030114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-book parts: - caption: Chapter 1 chapters: - file: content1 title: Content1 - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder numbered: true - caption: Chapter 2 chapters: - file: content2 title: Content2 - file: content3 title: Content3 numbered: true root: index title: Toc ���������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_numbered_parts_subset_.yml���������������0000664�0000000�0000000�00000000632�14536031355�0031477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-book parts: - caption: Chapter 1 chapters: - file: content1 title: Content1 numbered: true - caption: Chapter 2 chapters: - file: content2 title: Content2 - file: content3 title: Content3 - caption: Chapter 3 chapters: - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder numbered: true root: index title: Toc ������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_parts_.yml�������������������������������0000664�0000000�0000000�00000001052�14536031355�0026226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-book parts: - caption: A section chapters: - file: subfolder/index sections: - file: subfolder/asubpage1 title: Asubpage title: Subfolder - caption: Another section chapters: - file: content1 sections: - file: subfolder/asubpage2 title: Asubpage title: Content1 - file: content2 sections: - file: subfolder/asubpage3 title: Asubpage title: Content2 - file: content3 sections: - file: subfolder/asubpage4 title: Asubpage title: Content3 root: index title: Toc ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_simple_dict_.yml�������������������������0000664�0000000�0000000�00000000405�14536031355�0027372�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-article root: index sections: - file: content1 title: Content1 - file: content2 title: Content2 - file: content3 title: Content3 - file: subfolder/index sections: - file: subfolder/asubpage title: Asubpage title: Subfolder title: Toc �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tests/test_tools/test_migrate_jb_simple_list_.yml�������������������������0000664�0000000�0000000�00000000113�14536031355�0027416�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������format: jb-article root: index sections: - file: content1 - file: content2 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sphinx-external-toc-1.0.1/tox.ini�������������������������������������������������������������������0000664�0000000�0000000�00000001403�14536031355�0016743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# To use tox, see https://tox.readthedocs.io # Simply pip or conda install tox # If you use conda, you may also want to install tox-conda # then run `tox` or `tox -- {pytest args}` # run in parallel using `tox -p` [tox] envlist = py310 [testenv] usedevelop = true [testenv:py{39,310,311,312}] extras = testing commands = pytest {posargs} [testenv:cli] changedir={toxinidir} commands = sphinx-etoc {posargs} [testenv:docs-{update,clean}] extras = rtd whitelist_externals = rm echo setenv = update: SKIP_APIDOC = true commands = clean: rm -rf docs/_build sphinx-build -nW --keep-going -b {posargs:html} docs/ docs/_build/{posargs:html} commands_post = echo "open docs/_build/html/index.html" [flake8] max-line-length = 100 extend-ignore = E203 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������