diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 6a5c910..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,127 +0,0 @@ -# Circle CI configuration file -# https://circleci.com/docs/ - ---- -version: 2.1 - -####################################### -# Define some common steps as commands. -# - -commands: - check-skip: - steps: - - run: - name: Check-skip - command: | - export git_log=$(git log --max-count=1 --pretty=format:"%B" | - tr "\n" " ") - echo "Got commit message:" - echo "${git_log}" - if [[ -v CIRCLE_PULL_REQUEST ]] && ( \ - [[ "$git_log" == *"[skip circle]"* ]] || \ - [[ "$git_log" == *"[circle skip]"* ]]); then - echo "Skip detected, exiting job ${CIRCLE_JOB} for PR ${CIRCLE_PULL_REQUEST}." - circleci-agent step halt; - fi - - merge: - steps: - - run: - name: Merge with upstream - command: | - if ! git remote -v | grep upstream; then - git remote add upstream https://github.com/matplotlib/cycler.git - fi - git fetch upstream - if [[ "$CIRCLE_BRANCH" != "main" ]] && \ - [[ "$CIRCLE_PR_NUMBER" != "" ]]; then - echo "Merging ${CIRCLE_PR_NUMBER}" - git pull --ff-only upstream "refs/pull/${CIRCLE_PR_NUMBER}/merge" - fi - - pip-install: - description: Upgrade pip to get as clean an install as possible - steps: - - run: - name: Upgrade pip - command: | - python -m pip install --upgrade --user pip - - cycler-install: - steps: - - run: - name: Install Cycler - command: | - python -m pip install --user -ve .[docs] - - doc-build: - steps: - - restore_cache: - keys: - - sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }} - - sphinx-env-v1-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}-{{ .Environment.CIRCLE_JOB }} - - run: - name: Build documentation - command: | - # Set epoch to date of latest tag. - export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))" - mkdir -p logs - make html SPHINXOPTS="-T -j4 -w /tmp/sphinxerrorswarnings.log" - rm -r build/html/_sources - working_directory: doc - - save_cache: - key: sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }} - paths: - - doc/build/doctrees - - doc-show-errors-warnings: - steps: - - run: - name: Extract possible build errors and warnings - command: | - (grep "WARNING\|ERROR" /tmp/sphinxerrorswarnings.log || - echo "No errors or warnings") - # Save logs as an artifact, and convert from absolute paths to - # repository-relative paths. - sed "s~$PWD/~~" /tmp/sphinxerrorswarnings.log > \ - doc/logs/sphinx-errors-warnings.log - when: always - - store_artifacts: - path: doc/logs/sphinx-errors-warnings.log - -########################################## -# Here is where the real jobs are defined. -# - -jobs: - docs-python39: - docker: - - image: cimg/python:3.9 - resource_class: large - steps: - - checkout - - check-skip - - merge - - - pip-install - - - cycler-install - - - doc-build - - doc-show-errors-warnings - - - store_artifacts: - path: doc/build/html - -######################################### -# Defining workflows gets us parallelism. -# - -workflows: - version: 2 - build: - jobs: - # NOTE: If you rename this job, then you must update the `if` condition - # and `circleci-jobs` option in `.github/workflows/circleci.yml`. - - docs-python39 diff --git a/.circleci/fetch_doc_logs.py b/.circleci/fetch_doc_logs.py deleted file mode 100644 index 0a5552a..0000000 --- a/.circleci/fetch_doc_logs.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Download artifacts from CircleCI for a documentation build. - -This is run by the :file:`.github/workflows/circleci.yml` workflow in order to -get the warning/deprecation logs that will be posted on commits as checks. Logs -are downloaded from the :file:`docs/logs` artifact path and placed in the -:file:`logs` directory. - -Additionally, the artifact count for a build is produced as a workflow output, -by appending to the file specified by :env:`GITHUB_OUTPUT`. - -If there are no logs, an "ERROR" message is printed, but this is not fatal, as -the initial 'status' workflow runs when the build has first started, and there -are naturally no artifacts at that point. - -This script should be run by passing the CircleCI build URL as its first -argument. In the GitHub Actions workflow, this URL comes from -``github.event.target_url``. -""" -import json -import os -from pathlib import Path -import sys -from urllib.parse import urlparse -from urllib.request import URLError, urlopen - - -if len(sys.argv) != 2: - print('USAGE: fetch_doc_results.py CircleCI-build-url') - sys.exit(1) - -target_url = urlparse(sys.argv[1]) -*_, organization, repository, build_id = target_url.path.split('/') -print(f'Fetching artifacts from {organization}/{repository} for {build_id}') - -artifact_url = ( - f'https://circleci.com/api/v2/project/gh/' - f'{organization}/{repository}/{build_id}/artifacts' -) -print(artifact_url) -try: - with urlopen(artifact_url) as response: - artifacts = json.load(response) -except URLError: - artifacts = {'items': []} -artifact_count = len(artifacts['items']) -print(f'Found {artifact_count} artifacts') - -with open(os.environ['GITHUB_OUTPUT'], 'w+') as fd: - fd.write(f'count={artifact_count}\n') - -logs = Path('logs') -logs.mkdir(exist_ok=True) - -found = False -for item in artifacts['items']: - path = item['path'] - if path.startswith('doc/logs/'): - path = Path(path).name - print(f'Downloading {path} from {item["url"]}') - with urlopen(item['url']) as response: - (logs / path).write_bytes(response.read()) - found = True - -if not found: - print('ERROR: Did not find any artifact logs!') diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index dbcb44a..0000000 --- a/.coveragerc +++ /dev/null @@ -1,6 +0,0 @@ -[run] -source=cycler -branch=True -[report] -omit = - *test* diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 407d802..0000000 --- a/.flake8 +++ /dev/null @@ -1,26 +0,0 @@ -[flake8] -max-line-length = 88 -select = - # flake8 default - D, E, F, W, -ignore = - # flake8 default - E121,E123,E126,E226,E24,E704,W503,W504, - # pydocstyle - D100, D101, D102, D103, D104, D105, D106, - D200, D202, D204, D205, - D301, - D400, D401, D403, D404 - # ignored by pydocstyle numpy docstring convention - D107, D203, D212, D213, D402, D413, D415, D416, D417, - -exclude = - .git - build - # External files. - .tox - .eggs - -per-file-ignores = - setup.py: E402 -force-check = True diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml deleted file mode 100644 index 384bc8e..0000000 --- a/.github/workflows/circleci.yml +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: "CircleCI artifact handling" -on: [status] -jobs: - circleci_artifacts_redirector_job: - if: "${{ github.event.context == 'ci/circleci: docs-python39' }}" - permissions: - statuses: write - runs-on: ubuntu-latest - name: Run CircleCI artifacts redirector - steps: - - name: GitHub Action step - uses: larsoner/circleci-artifacts-redirector-action@master - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - artifact-path: 0/doc/build/html/index.html - circleci-jobs: docs-python39 - job-title: View the built docs - - post_warnings_as_review: - if: "${{ github.event.context == 'ci/circleci: docs-python39' }}" - permissions: - contents: read - checks: write - pull-requests: write - runs-on: ubuntu-latest - name: Post warnings/errors as review - steps: - - uses: actions/checkout@v3 - - - name: Fetch result artifacts - id: fetch-artifacts - run: | - python .circleci/fetch_doc_logs.py "${{ github.event.target_url }}" - - - name: Set up reviewdog - if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" - uses: reviewdog/action-setup@v1 - with: - reviewdog_version: latest - - - name: Post review - if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REVIEWDOG_SKIP_DOGHOUSE: "true" - CI_COMMIT: ${{ github.event.sha }} - CI_REPO_OWNER: ${{ github.event.repository.owner.login }} - CI_REPO_NAME: ${{ github.event.repository.name }} - run: | - # The 'status' event does not contain information in the way that - # reviewdog expects, so we unset those so it reads from the - # environment variables we set above. - unset GITHUB_ACTIONS GITHUB_EVENT_PATH - cat logs/sphinx-deprecations.log | \ - reviewdog \ - -efm '%f\:%l: %m' \ - -name=examples -tee -reporter=github-check -filter-mode=nofilter diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 024c8d9..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- - -name: Release -on: - release: - types: - - published - -jobs: - build: - name: Build Release Packages - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 10 - - - name: Set up Python - id: setup - uses: actions/setup-python@v4 - with: - python-version: 3.x - - - name: Install build tools - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade build - - - name: Build packages - run: python -m build - - - name: Save built packages as artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: packages-${{ runner.os }}-${{ steps.setup.outputs.python-version }} - path: dist/ - if-no-files-found: error - retention-days: 5 - - publish: - name: Upload release to PyPI - needs: build - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - steps: - - name: Download packages - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - pattern: packages-* - path: dist - merge-multiple: true - - - name: Print out packages - run: ls dist - - - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 - with: - subject-path: dist/cycler-* - - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3 diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml deleted file mode 100644 index ce95577..0000000 --- a/.github/workflows/reviewdog.yml +++ /dev/null @@ -1,79 +0,0 @@ ---- -name: Linting -on: - push: - branches-ignore: - - auto-backport-of-pr-[0-9]+ - - v[0-9]+.[0-9]+.[0-9x]+-doc - pull_request: - -jobs: - flake8: - name: flake8 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python 3 - uses: actions/setup-python@v4 - with: - python-version: 3.9 - - - name: Install flake8 - run: pip3 install flake8 - - - name: Set up reviewdog - run: | - mkdir -p "$HOME/bin" - curl -sfL \ - https://github.com/reviewdog/reviewdog/raw/master/install.sh | \ - sh -s -- -b "$HOME/bin" - echo "$HOME/bin" >> $GITHUB_PATH - - - name: Run flake8 - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -o pipefail - flake8 | \ - reviewdog -f=pep8 -name=flake8 \ - -tee -reporter=github-check -filter-mode nofilter - - mypy: - name: "Mypy" - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.9 - - - name: Set up reviewdog - run: | - mkdir -p "$HOME/bin" - curl -sfL \ - https://github.com/reviewdog/reviewdog/raw/master/install.sh | \ - sh -s -- -b "$HOME/bin" - echo "$HOME/bin" >> $GITHUB_PATH - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade mypy==1.5.1 - - - name: Install cycler - run: | - python -m pip install --no-deps -e . - - - name: Run mypy - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mypy cycler test_cycler.py | \ - reviewdog -f=mypy -name=mypy \ - -tee -reporter=github-check -filter-mode nofilter diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 98ceb87..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,45 +0,0 @@ ---- - -name: Tests - -on: - push: - branches-ignore: - - auto-backport-of-pr-[0-9]+ - - v[0-9]+.[0-9]+.[0-9x]+-doc - pull_request: - -jobs: - test: - name: "Python ${{ matrix.python-version }} ${{ matrix.name-suffix }}" - runs-on: ubuntu-20.04 - - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - - - name: Install cycler - run: | - python -m pip install .[tests] - - - name: Run pytest - run: | - pytest -raR -n auto --cov --cov-report= - - - name: Upload code coverage - uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 20908de..0000000 --- a/.gitignore +++ /dev/null @@ -1,83 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -bin/ -build/ -develop-eggs/ -dist/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg -doc/_build - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -cover/ -.coverage -.cache -.pytest_cache -nosetests.xml -coverage.xml -cover/ - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Rope -.ropeproject - -# Django stuff: -*.log -*.pot - -# Sphinx documentation -docs/_build/ -doc/source/generated/ - -#mac -.DS_Store -*~ - -#pycharm -.idea/* - -#Dolphin browser files -.directory/ -.directory - -#Binary data files -*.volume -*.am -*.tiff -*.tif -*.dat -*.DAT - -#generated documntation files -doc/resource/api/generated/ - -# ipython caches -.ipynb_checkpoints/ diff --git a/cycler/py.typed b/.nojekyll similarity index 100% rename from cycler/py.typed rename to .nojekyll diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d41d808..0000000 --- a/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2015, matplotlib project -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the matplotlib project nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.rst b/README.rst deleted file mode 100644 index 18712c9..0000000 --- a/README.rst +++ /dev/null @@ -1,21 +0,0 @@ -|PyPi|_ |Conda|_ |Supported Python versions|_ |GitHub Actions|_ |Codecov|_ - -.. |PyPi| image:: https://img.shields.io/pypi/v/cycler.svg?style=flat -.. _PyPi: https://pypi.python.org/pypi/cycler - -.. |Conda| image:: https://img.shields.io/conda/v/conda-forge/cycler -.. _Conda: https://anaconda.org/conda-forge/cycler - -.. |Supported Python versions| image:: https://img.shields.io/pypi/pyversions/cycler.svg -.. _Supported Python versions: https://pypi.python.org/pypi/cycler - -.. |GitHub Actions| image:: https://github.com/matplotlib/cycler/actions/workflows/tests.yml/badge.svg -.. _GitHub Actions: https://github.com/matplotlib/cycler/actions - -.. |Codecov| image:: https://codecov.io/github/matplotlib/cycler/badge.svg?branch=main&service=github -.. _Codecov: https://codecov.io/github/matplotlib/cycler?branch=main - -cycler: composable cycles -========================= - -Docs: https://matplotlib.org/cycler/ diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index b134e78..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,14 +0,0 @@ -# Security Policy - - -## Reporting a Vulnerability - - -To report a security vulnerability, please use the [Tidelift security -contact](https://tidelift.com/security). Tidelift will coordinate the fix and -disclosure. - -If you have found a security vulnerability, in order to keep it confidential, -please do not report an issue on GitHub. - -We do not award bounties for security vulnerabilities. diff --git a/_downloads/0886cd1ad3f1e8c1ad7ce523861a47e0/index-4.py b/_downloads/0886cd1ad3f1e8c1ad7ce523861a47e0/index-4.py new file mode 100644 index 0000000..85c9599 --- /dev/null +++ b/_downloads/0886cd1ad3f1e8c1ad7ce523861a47e0/index-4.py @@ -0,0 +1,20 @@ +fig, ax = plt.subplots(tight_layout=True) +x = np.linspace(0, 2*np.pi, 1024) + +for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])): + if i % 2: + ls = '-' + else: + ls = '--' + ax.plot(x, np.sin(x - i * np.pi / 4), + label=r'$\phi = {{{0}}} \pi / 4$'.format(i), + lw=lw + 1, + c=c, + ls=ls) + +ax.set_xlim([0, 2*np.pi]) +ax.set_title(r'$y=\sin(\theta + \phi)$') +ax.set_ylabel(r'[arb]') +ax.set_xlabel(r'$\theta$ [rad]') + +ax.legend(loc=0) \ No newline at end of file diff --git a/_downloads/091200287f54b5e1007e51e5d2b4e975/index-3.hires.png b/_downloads/091200287f54b5e1007e51e5d2b4e975/index-3.hires.png new file mode 100644 index 0000000..6b85c1c Binary files /dev/null and b/_downloads/091200287f54b5e1007e51e5d2b4e975/index-3.hires.png differ diff --git a/_downloads/145ac4d4fe50481dc12bead0aab95437/index-3.pdf b/_downloads/145ac4d4fe50481dc12bead0aab95437/index-3.pdf new file mode 100644 index 0000000..5142ab7 Binary files /dev/null and b/_downloads/145ac4d4fe50481dc12bead0aab95437/index-3.pdf differ diff --git a/_downloads/5710ee0df8b867f7b6d08f84831931d7/index-2.pdf b/_downloads/5710ee0df8b867f7b6d08f84831931d7/index-2.pdf new file mode 100644 index 0000000..fd50d39 Binary files /dev/null and b/_downloads/5710ee0df8b867f7b6d08f84831931d7/index-2.pdf differ diff --git a/_downloads/57bff23a880d1b8b7456f0ead4b9ed3c/index-1.png b/_downloads/57bff23a880d1b8b7456f0ead4b9ed3c/index-1.png new file mode 100644 index 0000000..c5f08fb Binary files /dev/null and b/_downloads/57bff23a880d1b8b7456f0ead4b9ed3c/index-1.png differ diff --git a/_downloads/605d27919a37b6f4b739afd44a433043/index-4.pdf b/_downloads/605d27919a37b6f4b739afd44a433043/index-4.pdf new file mode 100644 index 0000000..9248c9b Binary files /dev/null and b/_downloads/605d27919a37b6f4b739afd44a433043/index-4.pdf differ diff --git a/_downloads/732b8dd20da359d2e4252c17ed713218/index-4.hires.png b/_downloads/732b8dd20da359d2e4252c17ed713218/index-4.hires.png new file mode 100644 index 0000000..51514f7 Binary files /dev/null and b/_downloads/732b8dd20da359d2e4252c17ed713218/index-4.hires.png differ diff --git a/_downloads/7358fe54000965f7cad6f68eda5dea5d/index-3.py b/_downloads/7358fe54000965f7cad6f68eda5dea5d/index-3.py new file mode 100644 index 0000000..88a85c1 --- /dev/null +++ b/_downloads/7358fe54000965f7cad6f68eda5dea5d/index-3.py @@ -0,0 +1,15 @@ +fig, ax = plt.subplots(tight_layout=True) +x = np.linspace(0, 2*np.pi, 1024) + +for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])): + ax.plot(x, np.sin(x - i * np.pi / 4), + label=r'$\phi = {{{0}}} \pi / 4$'.format(i), + lw=lw + 1, + c=c) + +ax.set_xlim([0, 2*np.pi]) +ax.set_title(r'$y=\sin(\theta + \phi)$') +ax.set_ylabel(r'[arb]') +ax.set_xlabel(r'$\theta$ [rad]') + +ax.legend(loc=0) \ No newline at end of file diff --git a/_downloads/8bc86a4bedfb94acf98d26135927bfc9/index-1.hires.png b/_downloads/8bc86a4bedfb94acf98d26135927bfc9/index-1.hires.png new file mode 100644 index 0000000..a530acc Binary files /dev/null and b/_downloads/8bc86a4bedfb94acf98d26135927bfc9/index-1.hires.png differ diff --git a/_downloads/9b956b7cadd63dbc5972f4d3a604110d/index-4.png b/_downloads/9b956b7cadd63dbc5972f4d3a604110d/index-4.png new file mode 100644 index 0000000..7267eb4 Binary files /dev/null and b/_downloads/9b956b7cadd63dbc5972f4d3a604110d/index-4.png differ diff --git a/_downloads/c72cdcb2d8a2165c77e365185fcea378/index-1.pdf b/_downloads/c72cdcb2d8a2165c77e365185fcea378/index-1.pdf new file mode 100644 index 0000000..e2aa358 Binary files /dev/null and b/_downloads/c72cdcb2d8a2165c77e365185fcea378/index-1.pdf differ diff --git a/_downloads/ca4f939e75d32f21d99c47dd42caa575/index-1.py b/_downloads/ca4f939e75d32f21d99c47dd42caa575/index-1.py new file mode 100644 index 0000000..a9ffd42 --- /dev/null +++ b/_downloads/ca4f939e75d32f21d99c47dd42caa575/index-1.py @@ -0,0 +1,15 @@ +from cycler import cycler +from itertools import cycle + +fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, + figsize=(8, 4)) +x = np.arange(10) + +color_cycle = cycler(c=['r', 'g', 'b']) + +for i, sty in enumerate(color_cycle): + ax1.plot(x, x*(i+1), **sty) + + +for i, sty in zip(range(1, 5), cycle(color_cycle)): + ax2.plot(x, x*i, **sty) \ No newline at end of file diff --git a/_downloads/cf08f36bd983131e24621c94288ea0d9/index-2.hires.png b/_downloads/cf08f36bd983131e24621c94288ea0d9/index-2.hires.png new file mode 100644 index 0000000..2d297e1 Binary files /dev/null and b/_downloads/cf08f36bd983131e24621c94288ea0d9/index-2.hires.png differ diff --git a/_downloads/da097dda36ad3abf55fcd67455286f1b/index-2.png b/_downloads/da097dda36ad3abf55fcd67455286f1b/index-2.png new file mode 100644 index 0000000..af80a19 Binary files /dev/null and b/_downloads/da097dda36ad3abf55fcd67455286f1b/index-2.png differ diff --git a/_downloads/fbbf5b406a6762b380a0f13f3f68eee3/index-2.py b/_downloads/fbbf5b406a6762b380a0f13f3f68eee3/index-2.py new file mode 100644 index 0000000..8a0b595 --- /dev/null +++ b/_downloads/fbbf5b406a6762b380a0f13f3f68eee3/index-2.py @@ -0,0 +1,20 @@ +from cycler import cycler +from itertools import cycle + +fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, + figsize=(8, 4)) +x = np.arange(10) + +color_cycle = cycler(c=['r', 'g', 'b']) +ls_cycle = cycler('ls', ['-', '--']) +lw_cycle = cycler('lw', range(1, 4)) + +sty_cycle = ls_cycle * (color_cycle + lw_cycle) + +for i, sty in enumerate(sty_cycle): + ax1.plot(x, x*(i+1), **sty) + +sty_cycle = (color_cycle + lw_cycle) * ls_cycle + +for i, sty in enumerate(sty_cycle): + ax2.plot(x, x*(i+1), **sty) \ No newline at end of file diff --git a/_downloads/fc9ee615d2d3b6683fb182d98a22b0ae/index-3.png b/_downloads/fc9ee615d2d3b6683fb182d98a22b0ae/index-3.png new file mode 100644 index 0000000..eb6321c Binary files /dev/null and b/_downloads/fc9ee615d2d3b6683fb182d98a22b0ae/index-3.png differ diff --git a/_images/index-1.png b/_images/index-1.png new file mode 100644 index 0000000..c5f08fb Binary files /dev/null and b/_images/index-1.png differ diff --git a/_images/index-2.png b/_images/index-2.png new file mode 100644 index 0000000..af80a19 Binary files /dev/null and b/_images/index-2.png differ diff --git a/_images/index-3.png b/_images/index-3.png new file mode 100644 index 0000000..eb6321c Binary files /dev/null and b/_images/index-3.png differ diff --git a/_images/index-4.png b/_images/index-4.png new file mode 100644 index 0000000..7267eb4 Binary files /dev/null and b/_images/index-4.png differ diff --git a/_modules/cycler.html b/_modules/cycler.html new file mode 100644 index 0000000..f3e6075 --- /dev/null +++ b/_modules/cycler.html @@ -0,0 +1,696 @@ + + + + + + + cycler — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cycler

+"""
+Cycler
+======
+
+Cycling through combinations of values, producing dictionaries.
+
+You can add cyclers::
+
+    from cycler import cycler
+    cc = (cycler(color=list('rgb')) +
+          cycler(linestyle=['-', '--', '-.']))
+    for d in cc:
+        print(d)
+
+Results in::
+
+    {'color': 'r', 'linestyle': '-'}
+    {'color': 'g', 'linestyle': '--'}
+    {'color': 'b', 'linestyle': '-.'}
+
+
+You can multiply cyclers::
+
+    from cycler import cycler
+    cc = (cycler(color=list('rgb')) *
+          cycler(linestyle=['-', '--', '-.']))
+    for d in cc:
+        print(d)
+
+Results in::
+
+    {'color': 'r', 'linestyle': '-'}
+    {'color': 'r', 'linestyle': '--'}
+    {'color': 'r', 'linestyle': '-.'}
+    {'color': 'g', 'linestyle': '-'}
+    {'color': 'g', 'linestyle': '--'}
+    {'color': 'g', 'linestyle': '-.'}
+    {'color': 'b', 'linestyle': '-'}
+    {'color': 'b', 'linestyle': '--'}
+    {'color': 'b', 'linestyle': '-.'}
+"""
+
+
+from __future__ import annotations
+
+from collections.abc import Hashable, Iterable, Generator
+import copy
+from functools import reduce
+from itertools import product, cycle
+from operator import mul, add
+# Dict, List, Union required for runtime cast calls
+from typing import TypeVar, Generic, Callable, Union, Dict, List, Any, overload, cast
+
+__version__ = "0.12.1"
+
+K = TypeVar("K", bound=Hashable)
+L = TypeVar("L", bound=Hashable)
+V = TypeVar("V")
+U = TypeVar("U")
+
+
+def _process_keys(
+    left: Cycler[K, V] | Iterable[dict[K, V]],
+    right: Cycler[K, V] | Iterable[dict[K, V]] | None,
+) -> set[K]:
+    """
+    Helper function to compose cycler keys.
+
+    Parameters
+    ----------
+    left, right : iterable of dictionaries or None
+        The cyclers to be composed.
+
+    Returns
+    -------
+    keys : set
+        The keys in the composition of the two cyclers.
+    """
+    l_peek: dict[K, V] = next(iter(left)) if left != [] else {}
+    r_peek: dict[K, V] = next(iter(right)) if right is not None else {}
+    l_key: set[K] = set(l_peek.keys())
+    r_key: set[K] = set(r_peek.keys())
+    if l_key & r_key:
+        raise ValueError("Can not compose overlapping cycles")
+    return l_key | r_key
+
+
+
+[docs] +def concat(left: Cycler[K, V], right: Cycler[K, U]) -> Cycler[K, V | U]: + r""" + Concatenate `Cycler`\s, as if chained using `itertools.chain`. + + The keys must match exactly. + + Examples + -------- + >>> num = cycler('a', range(3)) + >>> let = cycler('a', 'abc') + >>> num.concat(let) + cycler('a', [0, 1, 2, 'a', 'b', 'c']) + + Returns + ------- + `Cycler` + The concatenated cycler. + """ + if left.keys != right.keys: + raise ValueError( + "Keys do not match:\n" + "\tIntersection: {both!r}\n" + "\tDisjoint: {just_one!r}".format( + both=left.keys & right.keys, just_one=left.keys ^ right.keys + ) + ) + _l = cast(Dict[K, List[Union[V, U]]], left.by_key()) + _r = cast(Dict[K, List[Union[V, U]]], right.by_key()) + return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
+ + + +
+[docs] +class Cycler(Generic[K, V]): + """ + Composable cycles. + + This class has compositions methods: + + ``+`` + for 'inner' products (zip) + + ``+=`` + in-place ``+`` + + ``*`` + for outer products (`itertools.product`) and integer multiplication + + ``*=`` + in-place ``*`` + + and supports basic slicing via ``[]``. + + Parameters + ---------- + left, right : Cycler or None + The 'left' and 'right' cyclers. + op : func or None + Function which composes the 'left' and 'right' cyclers. + """ + + def __call__(self): + return cycle(self) + +
+[docs] + def __init__( + self, + left: Cycler[K, V] | Iterable[dict[K, V]] | None, + right: Cycler[K, V] | None = None, + op: Any = None, + ): + """ + Semi-private init. + + Do not use this directly, use `cycler` function instead. + """ + if isinstance(left, Cycler): + self._left: Cycler[K, V] | list[dict[K, V]] = Cycler( + left._left, left._right, left._op + ) + elif left is not None: + # Need to copy the dictionary or else that will be a residual + # mutable that could lead to strange errors + self._left = [copy.copy(v) for v in left] + else: + self._left = [] + + if isinstance(right, Cycler): + self._right: Cycler[K, V] | None = Cycler( + right._left, right._right, right._op + ) + else: + self._right = None + + self._keys: set[K] = _process_keys(self._left, self._right) + self._op: Any = op
+ + + def __contains__(self, k): + return k in self._keys + + @property + def keys(self) -> set[K]: + """The keys this Cycler knows about.""" + return set(self._keys) + +
+[docs] + def change_key(self, old: K, new: K) -> None: + """ + Change a key in this cycler to a new name. + Modification is performed in-place. + + Does nothing if the old key is the same as the new key. + Raises a ValueError if the new key is already a key. + Raises a KeyError if the old key isn't a key. + """ + if old == new: + return + if new in self._keys: + raise ValueError( + f"Can't replace {old} with {new}, {new} is already a key" + ) + if old not in self._keys: + raise KeyError( + f"Can't replace {old} with {new}, {old} is not a key" + ) + + self._keys.remove(old) + self._keys.add(new) + + if self._right is not None and old in self._right.keys: + self._right.change_key(old, new) + + # self._left should always be non-None + # if self._keys is non-empty. + elif isinstance(self._left, Cycler): + self._left.change_key(old, new) + else: + # It should be completely safe at this point to + # assume that the old key can be found in each + # iteration. + self._left = [{new: entry[old]} for entry in self._left]
+ + + @classmethod + def _from_iter(cls, label: K, itr: Iterable[V]) -> Cycler[K, V]: + """ + Class method to create 'base' Cycler objects + that do not have a 'right' or 'op' and for which + the 'left' object is not another Cycler. + + Parameters + ---------- + label : hashable + The property key. + + itr : iterable + Finite length iterable of the property values. + + Returns + ------- + `Cycler` + New 'base' cycler. + """ + ret: Cycler[K, V] = cls(None) + ret._left = list({label: v} for v in itr) + ret._keys = {label} + return ret + + def __getitem__(self, key: slice) -> Cycler[K, V]: + # TODO : maybe add numpy style fancy slicing + if isinstance(key, slice): + trans = self.by_key() + return reduce(add, (_cycler(k, v[key]) for k, v in trans.items())) + else: + raise ValueError("Can only use slices with Cycler.__getitem__") + + def __iter__(self) -> Generator[dict[K, V], None, None]: + if self._right is None: + for left in self._left: + yield dict(left) + else: + if self._op is None: + raise TypeError( + "Operation cannot be None when both left and right are defined" + ) + for a, b in self._op(self._left, self._right): + out = {} + out.update(a) + out.update(b) + yield out + + def __add__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]: + """ + Pair-wise combine two equal length cyclers (zip). + + Parameters + ---------- + other : Cycler + """ + if len(self) != len(other): + raise ValueError( + f"Can only add equal length cycles, not {len(self)} and {len(other)}" + ) + return Cycler( + cast(Cycler[Union[K, L], Union[V, U]], self), + cast(Cycler[Union[K, L], Union[V, U]], other), + zip + ) + + @overload + def __mul__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]: + ... + + @overload + def __mul__(self, other: int) -> Cycler[K, V]: + ... + + def __mul__(self, other): + """ + Outer product of two cyclers (`itertools.product`) or integer + multiplication. + + Parameters + ---------- + other : Cycler or int + """ + if isinstance(other, Cycler): + return Cycler( + cast(Cycler[Union[K, L], Union[V, U]], self), + cast(Cycler[Union[K, L], Union[V, U]], other), + product + ) + elif isinstance(other, int): + trans = self.by_key() + return reduce( + add, (_cycler(k, v * other) for k, v in trans.items()) + ) + else: + return NotImplemented + + @overload + def __rmul__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]: + ... + + @overload + def __rmul__(self, other: int) -> Cycler[K, V]: + ... + + def __rmul__(self, other): + return self * other + + def __len__(self) -> int: + op_dict: dict[Callable, Callable[[int, int], int]] = {zip: min, product: mul} + if self._right is None: + return len(self._left) + l_len = len(self._left) + r_len = len(self._right) + return op_dict[self._op](l_len, r_len) + + # iadd and imul do not exapand the the type as the returns must be consistent with + # self, thus they flag as inconsistent with add/mul + def __iadd__(self, other: Cycler[K, V]) -> Cycler[K, V]: # type: ignore[misc] + """ + In-place pair-wise combine two equal length cyclers (zip). + + Parameters + ---------- + other : Cycler + """ + if not isinstance(other, Cycler): + raise TypeError("Cannot += with a non-Cycler object") + # True shallow copy of self is fine since this is in-place + old_self = copy.copy(self) + self._keys = _process_keys(old_self, other) + self._left = old_self + self._op = zip + self._right = Cycler(other._left, other._right, other._op) + return self + + def __imul__(self, other: Cycler[K, V] | int) -> Cycler[K, V]: # type: ignore[misc] + """ + In-place outer product of two cyclers (`itertools.product`). + + Parameters + ---------- + other : Cycler + """ + if not isinstance(other, Cycler): + raise TypeError("Cannot *= with a non-Cycler object") + # True shallow copy of self is fine since this is in-place + old_self = copy.copy(self) + self._keys = _process_keys(old_self, other) + self._left = old_self + self._op = product + self._right = Cycler(other._left, other._right, other._op) + return self + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Cycler): + return False + if len(self) != len(other): + return False + if self.keys ^ other.keys: + return False + return all(a == b for a, b in zip(self, other)) + + __hash__ = None # type: ignore + + def __repr__(self) -> str: + op_map = {zip: "+", product: "*"} + if self._right is None: + lab = self.keys.pop() + itr = list(v[lab] for v in self) + return f"cycler({lab!r}, {itr!r})" + else: + op = op_map.get(self._op, "?") + msg = "({left!r} {op} {right!r})" + return msg.format(left=self._left, op=op, right=self._right) + + def _repr_html_(self) -> str: + # an table showing the value of each key through a full cycle + output = "<table>" + sorted_keys = sorted(self.keys, key=repr) + for key in sorted_keys: + output += f"<th>{key!r}</th>" + for d in iter(self): + output += "<tr>" + for k in sorted_keys: + output += f"<td>{d[k]!r}</td>" + output += "</tr>" + output += "</table>" + return output + +
+[docs] + def by_key(self) -> dict[K, list[V]]: + """ + Values by key. + + This returns the transposed values of the cycler. Iterating + over a `Cycler` yields dicts with a single value for each key, + this method returns a `dict` of `list` which are the values + for the given key. + + The returned value can be used to create an equivalent `Cycler` + using only `+`. + + Returns + ------- + transpose : dict + dict of lists of the values for each key. + """ + + # TODO : sort out if this is a bottle neck, if there is a better way + # and if we care. + + keys = self.keys + out: dict[K, list[V]] = {k: list() for k in keys} + + for d in self: + for k in keys: + out[k].append(d[k]) + return out
+ + + # for back compatibility + _transpose = by_key + +
+[docs] + def simplify(self) -> Cycler[K, V]: + """ + Simplify the cycler into a sum (but no products) of cyclers. + + Returns + ------- + simple : Cycler + """ + # TODO: sort out if it is worth the effort to make sure this is + # balanced. Currently it is is + # (((a + b) + c) + d) vs + # ((a + b) + (c + d)) + # I would believe that there is some performance implications + trans = self.by_key() + return reduce(add, (_cycler(k, v) for k, v in trans.items()))
+ + + concat = concat
+ + + +@overload +def cycler(arg: Cycler[K, V]) -> Cycler[K, V]: + ... + + +@overload +def cycler(**kwargs: Iterable[V]) -> Cycler[str, V]: + ... + + +@overload +def cycler(label: K, itr: Iterable[V]) -> Cycler[K, V]: + ... + + +
+[docs] +def cycler(*args, **kwargs): + """ + Create a new `Cycler` object from a single positional argument, + a pair of positional arguments, or the combination of keyword arguments. + + cycler(arg) + cycler(label1=itr1[, label2=iter2[, ...]]) + cycler(label, itr) + + Form 1 simply copies a given `Cycler` object. + + Form 2 composes a `Cycler` as an inner product of the + pairs of keyword arguments. In other words, all of the + iterables are cycled simultaneously, as if through zip(). + + Form 3 creates a `Cycler` from a label and an iterable. + This is useful for when the label cannot be a keyword argument + (e.g., an integer or a name that has a space in it). + + Parameters + ---------- + arg : Cycler + Copy constructor for Cycler (does a shallow copy of iterables). + label : name + The property key. In the 2-arg form of the function, + the label can be any hashable object. In the keyword argument + form of the function, it must be a valid python identifier. + itr : iterable + Finite length iterable of the property values. + Can be a single-property `Cycler` that would + be like a key change, but as a shallow copy. + + Returns + ------- + cycler : Cycler + New `Cycler` for the given property + + """ + if args and kwargs: + raise TypeError( + "cycler() can only accept positional OR keyword arguments -- not both." + ) + + if len(args) == 1: + if not isinstance(args[0], Cycler): + raise TypeError( + "If only one positional argument given, it must " + "be a Cycler instance." + ) + return Cycler(args[0]) + elif len(args) == 2: + return _cycler(*args) + elif len(args) > 2: + raise TypeError( + "Only a single Cycler can be accepted as the lone " + "positional argument. Use keyword arguments instead." + ) + + if kwargs: + return reduce(add, (_cycler(k, v) for k, v in kwargs.items())) + + raise TypeError("Must have at least a positional OR keyword arguments")
+ + + +def _cycler(label: K, itr: Iterable[V]) -> Cycler[K, V]: + """ + Create a new `Cycler` object from a property name and iterable of values. + + Parameters + ---------- + label : hashable + The property key. + itr : iterable + Finite length iterable of the property values. + + Returns + ------- + cycler : Cycler + New `Cycler` for the given property + """ + if isinstance(itr, Cycler): + keys = itr.keys + if len(keys) != 1: + msg = "Can not create Cycler from a multi-property Cycler" + raise ValueError(msg) + + lab = keys.pop() + # Doesn't need to be a new list because + # _from_iter() will be creating that new list anyway. + itr = (v[lab] for v in itr) + + return Cycler._from_iter(label, itr) +
+ +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..5e4bcd0 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,101 @@ + + + + + + + Overview: module code — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

All modules for which code is available

+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/_sources/generated/cycler.Cycler.rst.txt b/_sources/generated/cycler.Cycler.rst.txt new file mode 100644 index 0000000..bb2d7fd --- /dev/null +++ b/_sources/generated/cycler.Cycler.rst.txt @@ -0,0 +1,32 @@ +cycler.Cycler +============= + +.. currentmodule:: cycler + +.. autoclass:: Cycler + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~Cycler.__init__ + ~Cycler.by_key + ~Cycler.change_key + ~Cycler.concat + ~Cycler.simplify + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~Cycler.keys + + \ No newline at end of file diff --git a/_sources/generated/cycler.concat.rst.txt b/_sources/generated/cycler.concat.rst.txt new file mode 100644 index 0000000..ecbb2e0 --- /dev/null +++ b/_sources/generated/cycler.concat.rst.txt @@ -0,0 +1,6 @@ +cycler.concat +============= + +.. currentmodule:: cycler + +.. autofunction:: concat \ No newline at end of file diff --git a/_sources/generated/cycler.cycler.rst.txt b/_sources/generated/cycler.cycler.rst.txt new file mode 100644 index 0000000..e79c29f --- /dev/null +++ b/_sources/generated/cycler.cycler.rst.txt @@ -0,0 +1,6 @@ +cycler.cycler +============= + +.. currentmodule:: cycler + +.. autofunction:: cycler \ No newline at end of file diff --git a/doc/source/index.rst b/_sources/index.rst.txt similarity index 100% rename from doc/source/index.rst rename to _sources/index.rst.txt diff --git a/_static/alabaster.css b/_static/alabaster.css new file mode 100644 index 0000000..e3174bf --- /dev/null +++ b/_static/alabaster.css @@ -0,0 +1,708 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Georgia, serif; + font-size: 17px; + background-color: #fff; + color: #000; + margin: 0; + padding: 0; +} + + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; + font-size: 14px; + line-height: 1.5; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #fff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar { + max-height: 100%; + overflow-y: auto; +} + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 160px; +} + +div.sphinxsidebar .search > div { + display: table-cell; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +div.sphinxsidebar .badge { + border-bottom: none; +} + +div.sphinxsidebar .badge:hover { + border-bottom: none; +} + +/* To address an issue with donation coming after search */ +div.sphinxsidebar h3.donation { + margin-top: 10px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Hide ugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..4157edf --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: inherit; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 0000000..2a924f1 --- /dev/null +++ b/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..d06a71d --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..8630a0b --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '0.12.1', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 0000000..250f566 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plot_directive.css b/_static/plot_directive.css new file mode 100644 index 0000000..d45593c --- /dev/null +++ b/_static/plot_directive.css @@ -0,0 +1,16 @@ +/* + * plot_directive.css + * ~~~~~~~~~~~~ + * + * Stylesheet controlling images created using the `plot` directive within + * Sphinx. + * + * :copyright: Copyright 2020-* by the Matplotlib development team. + * :license: Matplotlib, see LICENSE for details. + * + */ + +img.plot-directive { + border: 0; + max-width: 100%; +} diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000..0d49244 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000..7918c3f --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,574 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 57ca08c..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,40 +0,0 @@ -# AppVeyor.com is a Continuous Integration service to build and run tests under -# Windows - -image: Visual Studio 2019 - -environment: - matrix: - - PYTHON: "C:\\Python38" - - PYTHON: "C:\\Python38-x64" - - PYTHON: "C:\\Python39" - - PYTHON: "C:\\Python39-x64" - -install: - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - # Check that we have the expected version and architecture for Python - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - # Install the build and runtime dependencies of the project. - - "pip install -v pytest pytest-cov pytest-xdist" - - # Install the generated wheel package to test it - - "python setup.py install" - - -# Not a .NET project, we build scikit-image in the install step instead -build: false - -test_script: - - # Run unit tests with pytest - - "python -m pytest -raR -n auto" - -artifacts: - # Archive the generated wheel package in the ci.appveyor.com build report. - - path: dist\* - -#on_success: -# - TODO: upload the content of dist/*.whl to a public wheelhouse diff --git a/cycler/__init__.py b/cycler/__init__.py deleted file mode 100644 index db476b8..0000000 --- a/cycler/__init__.py +++ /dev/null @@ -1,581 +0,0 @@ -""" -Cycler -====== - -Cycling through combinations of values, producing dictionaries. - -You can add cyclers:: - - from cycler import cycler - cc = (cycler(color=list('rgb')) + - cycler(linestyle=['-', '--', '-.'])) - for d in cc: - print(d) - -Results in:: - - {'color': 'r', 'linestyle': '-'} - {'color': 'g', 'linestyle': '--'} - {'color': 'b', 'linestyle': '-.'} - - -You can multiply cyclers:: - - from cycler import cycler - cc = (cycler(color=list('rgb')) * - cycler(linestyle=['-', '--', '-.'])) - for d in cc: - print(d) - -Results in:: - - {'color': 'r', 'linestyle': '-'} - {'color': 'r', 'linestyle': '--'} - {'color': 'r', 'linestyle': '-.'} - {'color': 'g', 'linestyle': '-'} - {'color': 'g', 'linestyle': '--'} - {'color': 'g', 'linestyle': '-.'} - {'color': 'b', 'linestyle': '-'} - {'color': 'b', 'linestyle': '--'} - {'color': 'b', 'linestyle': '-.'} -""" - - -from __future__ import annotations - -from collections.abc import Hashable, Iterable, Generator -import copy -from functools import reduce -from itertools import product, cycle -from operator import mul, add -# Dict, List, Union required for runtime cast calls -from typing import TypeVar, Generic, Callable, Union, Dict, List, Any, overload, cast - - -__version__ = "0.13.0.dev0" - -K = TypeVar("K", bound=Hashable) -L = TypeVar("L", bound=Hashable) -V = TypeVar("V") -U = TypeVar("U") - - -def _process_keys( - left: Cycler[K, V] | Iterable[dict[K, V]], - right: Cycler[K, V] | Iterable[dict[K, V]] | None, -) -> set[K]: - """ - Helper function to compose cycler keys. - - Parameters - ---------- - left, right : iterable of dictionaries or None - The cyclers to be composed. - - Returns - ------- - keys : set - The keys in the composition of the two cyclers. - """ - l_peek: dict[K, V] = next(iter(left)) if left != [] else {} - r_peek: dict[K, V] = next(iter(right)) if right is not None else {} - l_key: set[K] = set(l_peek.keys()) - r_key: set[K] = set(r_peek.keys()) - if common_keys := l_key & r_key: - raise ValueError( - f"Cannot compose overlapping cycles, duplicate key(s): {common_keys}" - ) - - return l_key | r_key - - -def concat(left: Cycler[K, V], right: Cycler[K, U]) -> Cycler[K, V | U]: - r""" - Concatenate `Cycler`\s, as if chained using `itertools.chain`. - - The keys must match exactly. - - Examples - -------- - >>> num = cycler('a', range(3)) - >>> let = cycler('a', 'abc') - >>> num.concat(let) - cycler('a', [0, 1, 2, 'a', 'b', 'c']) - - Returns - ------- - `Cycler` - The concatenated cycler. - """ - if left.keys != right.keys: - raise ValueError( - "Keys do not match:\n" - "\tIntersection: {both!r}\n" - "\tDisjoint: {just_one!r}".format( - both=left.keys & right.keys, just_one=left.keys ^ right.keys - ) - ) - _l = cast(Dict[K, List[Union[V, U]]], left.by_key()) - _r = cast(Dict[K, List[Union[V, U]]], right.by_key()) - return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys)) - - -class Cycler(Generic[K, V]): - """ - Composable cycles. - - This class has compositions methods: - - ``+`` - for 'inner' products (zip) - - ``+=`` - in-place ``+`` - - ``*`` - for outer products (`itertools.product`) and integer multiplication - - ``*=`` - in-place ``*`` - - and supports basic slicing via ``[]``. - - Parameters - ---------- - left, right : Cycler or None - The 'left' and 'right' cyclers. - op : func or None - Function which composes the 'left' and 'right' cyclers. - """ - - def __call__(self): - return cycle(self) - - def __init__( - self, - left: Cycler[K, V] | Iterable[dict[K, V]] | None, - right: Cycler[K, V] | None = None, - op: Any = None, - ): - """ - Semi-private init. - - Do not use this directly, use `cycler` function instead. - """ - if isinstance(left, Cycler): - self._left: Cycler[K, V] | list[dict[K, V]] = Cycler( - left._left, left._right, left._op - ) - elif left is not None: - # Need to copy the dictionary or else that will be a residual - # mutable that could lead to strange errors - self._left = [copy.copy(v) for v in left] - else: - self._left = [] - - if isinstance(right, Cycler): - self._right: Cycler[K, V] | None = Cycler( - right._left, right._right, right._op - ) - else: - self._right = None - - self._keys: set[K] = _process_keys(self._left, self._right) - self._op: Any = op - - def __contains__(self, k): - return k in self._keys - - @property - def keys(self) -> set[K]: - """The keys this Cycler knows about.""" - return set(self._keys) - - def change_key(self, old: K, new: K) -> None: - """ - Change a key in this cycler to a new name. - Modification is performed in-place. - - Does nothing if the old key is the same as the new key. - Raises a ValueError if the new key is already a key. - Raises a KeyError if the old key isn't a key. - """ - if old == new: - return - if new in self._keys: - raise ValueError( - f"Can't replace {old} with {new}, {new} is already a key" - ) - if old not in self._keys: - raise KeyError( - f"Can't replace {old} with {new}, {old} is not a key" - ) - - self._keys.remove(old) - self._keys.add(new) - - if self._right is not None and old in self._right.keys: - self._right.change_key(old, new) - - # self._left should always be non-None - # if self._keys is non-empty. - elif isinstance(self._left, Cycler): - self._left.change_key(old, new) - else: - # It should be completely safe at this point to - # assume that the old key can be found in each - # iteration. - self._left = [{new: entry[old]} for entry in self._left] - - @classmethod - def _from_iter(cls, label: K, itr: Iterable[V]) -> Cycler[K, V]: - """ - Class method to create 'base' Cycler objects - that do not have a 'right' or 'op' and for which - the 'left' object is not another Cycler. - - Parameters - ---------- - label : hashable - The property key. - - itr : iterable - Finite length iterable of the property values. - - Returns - ------- - `Cycler` - New 'base' cycler. - """ - ret: Cycler[K, V] = cls(None) - ret._left = list({label: v} for v in itr) - ret._keys = {label} - return ret - - def __getitem__(self, key: slice) -> Cycler[K, V]: - # TODO : maybe add numpy style fancy slicing - if isinstance(key, slice): - trans = self.by_key() - return reduce(add, (_cycler(k, v[key]) for k, v in trans.items())) - else: - raise ValueError("Can only use slices with Cycler.__getitem__") - - def __iter__(self) -> Generator[dict[K, V], None, None]: - if self._right is None: - for left in self._left: - yield dict(left) - else: - if self._op is None: - raise TypeError( - "Operation cannot be None when both left and right are defined" - ) - for a, b in self._op(self._left, self._right): - out = {} - out.update(a) - out.update(b) - yield out - - def __add__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]: - """ - Pair-wise combine two equal length cyclers (zip). - - Parameters - ---------- - other : Cycler - """ - if len(self) != len(other): - raise ValueError( - f"Can only add equal length cycles, not {len(self)} and {len(other)}" - ) - return Cycler( - cast(Cycler[Union[K, L], Union[V, U]], self), - cast(Cycler[Union[K, L], Union[V, U]], other), - zip - ) - - @overload - def __mul__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]: - ... - - @overload - def __mul__(self, other: int) -> Cycler[K, V]: - ... - - def __mul__(self, other): - """ - Outer product of two cyclers (`itertools.product`) or integer - multiplication. - - Parameters - ---------- - other : Cycler or int - """ - if isinstance(other, Cycler): - return Cycler( - cast(Cycler[Union[K, L], Union[V, U]], self), - cast(Cycler[Union[K, L], Union[V, U]], other), - product - ) - elif isinstance(other, int): - trans = self.by_key() - return reduce( - add, (_cycler(k, v * other) for k, v in trans.items()) - ) - else: - return NotImplemented - - @overload - def __rmul__(self, other: Cycler[L, U]) -> Cycler[K | L, V | U]: - ... - - @overload - def __rmul__(self, other: int) -> Cycler[K, V]: - ... - - def __rmul__(self, other): - return self * other - - def __len__(self) -> int: - op_dict: dict[Callable, Callable[[int, int], int]] = {zip: min, product: mul} - if self._right is None: - return len(self._left) - l_len = len(self._left) - r_len = len(self._right) - return op_dict[self._op](l_len, r_len) - - # iadd and imul do not exapand the the type as the returns must be consistent with - # self, thus they flag as inconsistent with add/mul - def __iadd__(self, other: Cycler[K, V]) -> Cycler[K, V]: # type: ignore[misc] - """ - In-place pair-wise combine two equal length cyclers (zip). - - Parameters - ---------- - other : Cycler - """ - if not isinstance(other, Cycler): - raise TypeError("Cannot += with a non-Cycler object") - if len(self) != len(other): - raise ValueError( - f"Can only add equal length cycles, not {len(self)} and {len(other)}" - ) - # True shallow copy of self is fine since this is in-place - old_self = copy.copy(self) - self._keys = _process_keys(old_self, other) - self._left = old_self - self._op = zip - self._right = Cycler(other._left, other._right, other._op) - return self - - def __imul__(self, other: Cycler[K, V] | int) -> Cycler[K, V]: # type: ignore[misc] - """ - In-place outer product of two cyclers (`itertools.product`). - - Parameters - ---------- - other : Cycler - """ - if not isinstance(other, Cycler): - raise TypeError("Cannot *= with a non-Cycler object") - # True shallow copy of self is fine since this is in-place - old_self = copy.copy(self) - self._keys = _process_keys(old_self, other) - self._left = old_self - self._op = product - self._right = Cycler(other._left, other._right, other._op) - return self - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Cycler): - return False - if len(self) != len(other): - return False - if self.keys ^ other.keys: - return False - return all(a == b for a, b in zip(self, other)) - - __hash__ = None # type: ignore - - def __repr__(self) -> str: - op_map = {zip: "+", product: "*"} - if self._right is None: - lab = self.keys.pop() - itr = list(v[lab] for v in self) - return f"cycler({lab!r}, {itr!r})" - else: - op = op_map.get(self._op, "?") - msg = "({left!r} {op} {right!r})" - return msg.format(left=self._left, op=op, right=self._right) - - def _repr_html_(self) -> str: - # an table showing the value of each key through a full cycle - output = "" - sorted_keys = sorted(self.keys, key=repr) - for key in sorted_keys: - output += f"" - for d in iter(self): - output += "" - for k in sorted_keys: - output += f"" - output += "" - output += "
{key!r}
{d[k]!r}
" - return output - - def by_key(self) -> dict[K, list[V]]: - """ - Values by key. - - This returns the transposed values of the cycler. Iterating - over a `Cycler` yields dicts with a single value for each key, - this method returns a `dict` of `list` which are the values - for the given key. - - The returned value can be used to create an equivalent `Cycler` - using only `+`. - - Returns - ------- - transpose : dict - dict of lists of the values for each key. - """ - - # TODO : sort out if this is a bottle neck, if there is a better way - # and if we care. - - keys = self.keys - out: dict[K, list[V]] = {k: list() for k in keys} - - for d in self: - for k in keys: - out[k].append(d[k]) - return out - - # for back compatibility - _transpose = by_key - - def simplify(self) -> Cycler[K, V]: - """ - Simplify the cycler into a sum (but no products) of cyclers. - - Returns - ------- - simple : Cycler - """ - # TODO: sort out if it is worth the effort to make sure this is - # balanced. Currently it is is - # (((a + b) + c) + d) vs - # ((a + b) + (c + d)) - # I would believe that there is some performance implications - trans = self.by_key() - return reduce(add, (_cycler(k, v) for k, v in trans.items())) - - concat = concat - - -@overload -def cycler(arg: Cycler[K, V]) -> Cycler[K, V]: - ... - - -@overload -def cycler(**kwargs: Iterable[V]) -> Cycler[str, V]: - ... - - -@overload -def cycler(label: K, itr: Iterable[V]) -> Cycler[K, V]: - ... - - -def cycler(*args, **kwargs): - """ - Create a new `Cycler` object from a single positional argument, - a pair of positional arguments, or the combination of keyword arguments. - - cycler(arg) - cycler(label1=itr1[, label2=iter2[, ...]]) - cycler(label, itr) - - Form 1 simply copies a given `Cycler` object. - - Form 2 composes a `Cycler` as an inner product of the - pairs of keyword arguments. In other words, all of the - iterables are cycled simultaneously, as if through zip(). - - Form 3 creates a `Cycler` from a label and an iterable. - This is useful for when the label cannot be a keyword argument - (e.g., an integer or a name that has a space in it). - - Parameters - ---------- - arg : Cycler - Copy constructor for Cycler (does a shallow copy of iterables). - label : name - The property key. In the 2-arg form of the function, - the label can be any hashable object. In the keyword argument - form of the function, it must be a valid python identifier. - itr : iterable - Finite length iterable of the property values. - Can be a single-property `Cycler` that would - be like a key change, but as a shallow copy. - - Returns - ------- - cycler : Cycler - New `Cycler` for the given property - - """ - if args and kwargs: - raise TypeError( - "cycler() can only accept positional OR keyword arguments -- not both." - ) - - if len(args) == 1: - if not isinstance(args[0], Cycler): - raise TypeError( - "If only one positional argument given, it must " - "be a Cycler instance." - ) - return Cycler(args[0]) - elif len(args) == 2: - return _cycler(*args) - elif len(args) > 2: - raise TypeError( - "Only a single Cycler can be accepted as the lone " - "positional argument. Use keyword arguments instead." - ) - - if kwargs: - return reduce(add, (_cycler(k, v) for k, v in kwargs.items())) - - raise TypeError("Must have at least a positional OR keyword arguments") - - -def _cycler(label: K, itr: Iterable[V]) -> Cycler[K, V]: - """ - Create a new `Cycler` object from a property name and iterable of values. - - Parameters - ---------- - label : hashable - The property key. - itr : iterable - Finite length iterable of the property values. - - Returns - ------- - cycler : Cycler - New `Cycler` for the given property - """ - if isinstance(itr, Cycler): - keys = itr.keys - if len(keys) != 1: - msg = "Can not create Cycler from a multi-property Cycler" - raise ValueError(msg) - - lab = keys.pop() - # Doesn't need to be a new list because - # _from_iter() will be creating that new list anyway. - itr = (v[lab] for v in itr) - - return Cycler._from_iter(label, itr) diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index dc1b4c8..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/cycler.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/cycler.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/cycler" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/cycler" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/_templates/autosummary/class.rst b/doc/_templates/autosummary/class.rst deleted file mode 100644 index 851259b..0000000 --- a/doc/_templates/autosummary/class.rst +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "!autosummary/class.rst" %} - -{% block methods %} -{% if methods %} - - .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages. - .. autosummary:: - :toctree: - {% for item in all_methods %} - {%- if not item.startswith('_') or item in ['__call__'] %} - {{ name }}.{{ item }} - {%- endif -%} - {%- endfor %} - -{% endif %} -{% endblock %} - -{% block attributes %} -{% if attributes %} - - .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages. - .. autosummary:: - :toctree: - {% for item in all_attributes %} - {%- if not item.startswith('_') %} - {{ name }}.{{ item }} - {%- endif -%} - {%- endfor %} - -{% endif %} -{% endblock %} diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index 807c007..0000000 --- a/doc/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\cycler.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\cycler.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 906a782..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,279 +0,0 @@ -#!/usr/bin/env python3 -# -# cycler documentation build configuration file, created by -# sphinx-quickstart on Wed Jul 1 13:32:53 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.3' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.autosummary', - 'matplotlib.sphinxext.plot_directive', - 'IPython.sphinxext.ipython_directive', - 'IPython.sphinxext.ipython_console_highlighting', - 'numpydoc', -] - - -autosummary_generate = True - -numpydoc_show_class_members = False -autodoc_default_options = {'members': True} - - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'cycler' -copyright = '2015, Matplotlib Developers' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The full version, including alpha/beta/rc tags. -from cycler import __version__ as release # noqa -# The short X.Y version. -version = '.'.join(release.split('.')[:2]) - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -default_role = 'obj' - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = 'basic' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = [] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'cyclerdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'cycler.tex', 'cycler Documentation', - 'Matplotlib Developers', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'cycler', 'cycler Documentation', - ['Matplotlib Developers'], 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'cycler', 'cycler Documentation', - 'Matplotlib Developers', 'cycler', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False - -intersphinx_mapping = {'python': ('https://docs.python.org/3', None), - 'matplotlb': ('https://matplotlib.org', None)} - -# ################ numpydoc config #################### -numpydoc_show_class_members = False diff --git a/generated/cycler.Cycler.html b/generated/cycler.Cycler.html new file mode 100644 index 0000000..219d35d --- /dev/null +++ b/generated/cycler.Cycler.html @@ -0,0 +1,255 @@ + + + + + + + + cycler.Cycler — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

cycler.Cycler

+
+
+class cycler.Cycler(left: Cycler[K, V] | Iterable[dict[K, V]] | None, right: Cycler[K, V] | None = None, op: Any = None)[source]
+

Composable cycles.

+

This class has compositions methods:

+
+
+

for ‘inner’ products (zip)

+
+
+=

in-place +

+
+
*

for outer products (itertools.product) and integer multiplication

+
+
*=

in-place *

+
+
+

and supports basic slicing via [].

+
+
Parameters:
+
+
left, rightCycler or None

The ‘left’ and ‘right’ cyclers.

+
+
opfunc or None

Function which composes the ‘left’ and ‘right’ cyclers.

+
+
+
+
+
+
+__init__(left: Cycler[K, V] | Iterable[dict[K, V]] | None, right: Cycler[K, V] | None = None, op: Any = None)[source]
+

Semi-private init.

+

Do not use this directly, use cycler function instead.

+
+ +

Methods

+ + + + + + + + + + + + + + + + + + +

__init__(left[, right, op])

Semi-private init.

by_key()

Values by key.

change_key(old, new)

Change a key in this cycler to a new name.

concat(right)

Concatenate Cyclers, as if chained using itertools.chain.

simplify()

Simplify the cycler into a sum (but no products) of cyclers.

+

Attributes

+ + + + + + +

keys

The keys this Cycler knows about.

+
+
+by_key() dict[K, list[V]][source]
+

Values by key.

+

This returns the transposed values of the cycler. Iterating +over a Cycler yields dicts with a single value for each key, +this method returns a dict of list which are the values +for the given key.

+

The returned value can be used to create an equivalent Cycler +using only +.

+
+
Returns:
+
+
transposedict

dict of lists of the values for each key.

+
+
+
+
+
+ +
+
+change_key(old: K, new: K) None[source]
+

Change a key in this cycler to a new name. +Modification is performed in-place.

+

Does nothing if the old key is the same as the new key. +Raises a ValueError if the new key is already a key. +Raises a KeyError if the old key isn’t a key.

+
+ +
+
+concat(right: Cycler[K, U]) Cycler[K, V | U]
+

Concatenate Cyclers, as if chained using itertools.chain.

+

The keys must match exactly.

+
+
Returns:
+
+
Cycler

The concatenated cycler.

+
+
+
+
+

Examples

+
>>> num = cycler('a', range(3))
+>>> let = cycler('a', 'abc')
+>>> num.concat(let)
+cycler('a', [0, 1, 2, 'a', 'b', 'c'])
+
+
+
+ +
+
+property keys: set[K]
+

The keys this Cycler knows about.

+
+ +
+
+simplify() Cycler[K, V][source]
+

Simplify the cycler into a sum (but no products) of cyclers.

+
+
Returns:
+
+
simpleCycler
+
+
+
+
+ +
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/generated/cycler.concat.html b/generated/cycler.concat.html new file mode 100644 index 0000000..579e43a --- /dev/null +++ b/generated/cycler.concat.html @@ -0,0 +1,133 @@ + + + + + + + + cycler.concat — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

cycler.concat

+
+
+cycler.concat(left: Cycler[K, V], right: Cycler[K, U]) Cycler[K, V | U][source]
+

Concatenate Cyclers, as if chained using itertools.chain.

+

The keys must match exactly.

+
+
Returns:
+
+
Cycler

The concatenated cycler.

+
+
+
+
+

Examples

+
>>> num = cycler('a', range(3))
+>>> let = cycler('a', 'abc')
+>>> num.concat(let)
+cycler('a', [0, 1, 2, 'a', 'b', 'c'])
+
+
+
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/generated/cycler.cycler.html b/generated/cycler.cycler.html new file mode 100644 index 0000000..a5d8f2c --- /dev/null +++ b/generated/cycler.cycler.html @@ -0,0 +1,156 @@ + + + + + + + + cycler.cycler — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

cycler.cycler

+
+
+cycler.cycler(arg: Cycler[K, V]) Cycler[K, V][source]
+
+cycler.cycler(**kwargs: Iterable[V]) Cycler[str, V]
+
+cycler.cycler(label: K, itr: Iterable[V]) Cycler[K, V]
+

Create a new Cycler object from a single positional argument, +a pair of positional arguments, or the combination of keyword arguments.

+

cycler(arg) +cycler(label1=itr1[, label2=iter2[, …]]) +cycler(label, itr)

+

Form 1 simply copies a given Cycler object.

+

Form 2 composes a Cycler as an inner product of the +pairs of keyword arguments. In other words, all of the +iterables are cycled simultaneously, as if through zip().

+

Form 3 creates a Cycler from a label and an iterable. +This is useful for when the label cannot be a keyword argument +(e.g., an integer or a name that has a space in it).

+
+
Parameters:
+
+
argCycler

Copy constructor for Cycler (does a shallow copy of iterables).

+
+
labelname

The property key. In the 2-arg form of the function, +the label can be any hashable object. In the keyword argument +form of the function, it must be a valid python identifier.

+
+
itriterable

Finite length iterable of the property values. +Can be a single-property Cycler that would +be like a key change, but as a shallow copy.

+
+
+
+
Returns:
+
+
cyclerCycler

New Cycler for the given property

+
+
+
+
+
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000..5cfbd65 --- /dev/null +++ b/genindex.html @@ -0,0 +1,162 @@ + + + + + + + Index — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..f59ef44 --- /dev/null +++ b/index.html @@ -0,0 +1,581 @@ + + + + + + + + Composable cycles — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

Composable cycles

+
+
Version:
+

0.12

+
+
Date:
+

Jan 18, 2024

+
+
+ + + + + + + + + + + + +

Docs

https://matplotlib.org/cycler

PyPI

https://pypi.python.org/pypi/Cycler

GitHub

https://github.com/matplotlib/cycler

+
+

cycler API

+ + + + + + + + + + + + +

cycler()

Create a new Cycler object from a single positional argument, a pair of positional arguments, or the combination of keyword arguments.

Cycler(left[, right, op])

Composable cycles.

concat(left, right)

Concatenate Cyclers, as if chained using itertools.chain.

+

The public API of cycler consists of a class Cycler, a +factory function cycler(), and a concatenation function +concat(). The factory function provides a simple interface for +creating ‘base’ Cycler objects while the class takes care of the +composition and iteration logic.

+
+
+

Cycler Usage

+
+

Base

+

A single entry Cycler object can be used to easily cycle over a single style. +To create the Cycler use the cycler() function to link a +key/style/keyword argument to series of values. The key must be hashable (as it +will eventually be used as the key in a dict).

+
In [1]: from __future__ import print_function
+
+In [2]: from cycler import cycler
+
+In [3]: color_cycle = cycler(color=['r', 'g', 'b'])
+
+In [4]: color_cycle
+Out[4]: cycler('color', ['r', 'g', 'b'])
+
+
+

The Cycler knows its length and keys:

+
In [5]: len(color_cycle)
+Out[5]: 3
+
+In [6]: color_cycle.keys
+Out[6]: {'color'}
+
+
+

Iterating over this object will yield a series of dict objects keyed on +the label

+
In [7]: for v in color_cycle:
+   ...:     print(v)
+   ...: 
+{'color': 'r'}
+{'color': 'g'}
+{'color': 'b'}
+
+
+

Cycler objects can be passed as the argument to cycler() +which returns a new Cycler with a new label, but the same values.

+
In [8]: cycler(ec=color_cycle)
+Out[8]: cycler('ec', ['r', 'g', 'b'])
+
+
+

Iterating over a Cycler results in the finite list of entries, to +get an infinite cycle, call the Cycler object (a-la a generator)

+
In [9]: cc = color_cycle()
+
+In [10]: for j, c in zip(range(5),  cc):
+   ....:     print(j, c)
+   ....: 
+0 {'color': 'r'}
+1 {'color': 'g'}
+2 {'color': 'b'}
+3 {'color': 'r'}
+4 {'color': 'g'}
+
+
+
+
+

Composition

+

A single Cycler can just as easily be replaced by a single for +loop. The power of Cycler objects is that they can be composed to easily +create complex multi-key cycles.

+
+

Addition

+

Equal length Cyclers with different keys can be added to get the +‘inner’ product of two cycles

+
In [11]: lw_cycle = cycler(lw=range(1, 4))
+
+In [12]: wc = lw_cycle + color_cycle
+
+
+

The result has the same length and has keys which are the union of the +two input Cycler’s.

+
In [13]: len(wc)
+Out[13]: 3
+
+In [14]: wc.keys
+Out[14]: {'color', 'lw'}
+
+
+

and iterating over the result is the zip of the two input cycles

+
In [15]: for s in wc:
+   ....:     print(s)
+   ....: 
+{'lw': 1, 'color': 'r'}
+{'lw': 2, 'color': 'g'}
+{'lw': 3, 'color': 'b'}
+
+
+

As with arithmetic, addition is commutative

+
In [16]: lw_c = lw_cycle + color_cycle
+
+In [17]: c_lw = color_cycle + lw_cycle
+
+In [18]: for j, (a, b) in enumerate(zip(lw_c, c_lw)):
+   ....:    print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
+   ....: 
+(0) A: {'lw': 1, 'color': 'r'} B: {'color': 'r', 'lw': 1}
+(1) A: {'lw': 2, 'color': 'g'} B: {'color': 'g', 'lw': 2}
+(2) A: {'lw': 3, 'color': 'b'} B: {'color': 'b', 'lw': 3}
+
+
+

For convenience, the cycler() function can have multiple +key-value pairs and will automatically compose them into a single +Cycler via addition

+
In [19]: wc = cycler(c=['r', 'g', 'b'], lw=range(3))
+
+In [20]: for s in wc:
+   ....:     print(s)
+   ....: 
+{'c': 'r', 'lw': 0}
+{'c': 'g', 'lw': 1}
+{'c': 'b', 'lw': 2}
+
+
+
+
+

Multiplication

+

Any pair of Cycler can be multiplied

+
In [21]: m_cycle = cycler(marker=['s', 'o'])
+
+In [22]: m_c = m_cycle * color_cycle
+
+
+

which gives the ‘outer product’ of the two cycles (same as +itertools.product() )

+
In [23]: len(m_c)
+Out[23]: 6
+
+In [24]: m_c.keys
+Out[24]: {'color', 'marker'}
+
+In [25]: for s in m_c:
+   ....:     print(s)
+   ....: 
+{'marker': 's', 'color': 'r'}
+{'marker': 's', 'color': 'g'}
+{'marker': 's', 'color': 'b'}
+{'marker': 'o', 'color': 'r'}
+{'marker': 'o', 'color': 'g'}
+{'marker': 'o', 'color': 'b'}
+
+
+

Note that unlike addition, multiplication is not commutative (like +matrices)

+
In [26]: c_m = color_cycle * m_cycle
+
+In [27]: for j, (a, b) in enumerate(zip(c_m, m_c)):
+   ....:    print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
+   ....: 
+(0) A: {'color': 'r', 'marker': 's'} B: {'marker': 's', 'color': 'r'}
+(1) A: {'color': 'r', 'marker': 'o'} B: {'marker': 's', 'color': 'g'}
+(2) A: {'color': 'g', 'marker': 's'} B: {'marker': 's', 'color': 'b'}
+(3) A: {'color': 'g', 'marker': 'o'} B: {'marker': 'o', 'color': 'r'}
+(4) A: {'color': 'b', 'marker': 's'} B: {'marker': 'o', 'color': 'g'}
+(5) A: {'color': 'b', 'marker': 'o'} B: {'marker': 'o', 'color': 'b'}
+
+
+
+
+

Integer Multiplication

+

Cyclers can also be multiplied by integer values to increase the length.

+
In [28]: color_cycle * 2
+Out[28]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
+
+In [29]: 2 * color_cycle
+Out[29]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
+
+
+
+
+

Concatenation

+

Cycler objects can be concatenated either via the Cycler.concat() method

+
In [30]: color_cycle.concat(color_cycle)
+Out[30]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
+
+
+

or the top-level concat() function

+
In [31]: from cycler import concat
+
+In [32]: concat(color_cycle, color_cycle)
+Out[32]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
+
+
+
+
+
+

Slicing

+

Cycles can be sliced with slice objects

+
In [33]: color_cycle[::-1]
+Out[33]: cycler('color', ['b', 'g', 'r'])
+
+In [34]: color_cycle[:2]
+Out[34]: cycler('color', ['r', 'g'])
+
+In [35]: color_cycle[1:]
+Out[35]: cycler('color', ['g', 'b'])
+
+
+

to return a sub-set of the cycle as a new Cycler.

+
+
+

Inspecting the Cycler

+

To inspect the values of the transposed Cycler use +the Cycler.by_key method:

+
In [36]: c_m.by_key()
+Out[36]: 
+{'marker': ['s', 'o', 's', 'o', 's', 'o'],
+ 'color': ['r', 'r', 'g', 'g', 'b', 'b']}
+
+
+

This dict can be mutated and used to create a new Cycler with +the updated values

+
In [37]: bk = c_m.by_key()
+
+In [38]: bk['color'] = ['green'] * len(c_m)
+
+In [39]: cycler(**bk)
+Out[39]: (cycler('marker', ['s', 'o', 's', 'o', 's', 'o']) + cycler('color', ['green', 'green', 'green', 'green', 'green', 'green']))
+
+
+
+
+

Examples

+

We can use Cycler instances to cycle over one or more kwarg to +plot :

+
from cycler import cycler
+from itertools import cycle
+
+fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
+                               figsize=(8, 4))
+x = np.arange(10)
+
+color_cycle = cycler(c=['r', 'g', 'b'])
+
+for i, sty in enumerate(color_cycle):
+   ax1.plot(x, x*(i+1), **sty)
+
+
+for i, sty in zip(range(1, 5), cycle(color_cycle)):
+   ax2.plot(x, x*i, **sty)
+
+
+

(Source code, png, hires.png, pdf)

+
+_images/index-1.png +
+
from cycler import cycler
+from itertools import cycle
+
+fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
+                               figsize=(8, 4))
+x = np.arange(10)
+
+color_cycle = cycler(c=['r', 'g', 'b'])
+ls_cycle = cycler('ls', ['-', '--'])
+lw_cycle = cycler('lw', range(1, 4))
+
+sty_cycle = ls_cycle * (color_cycle + lw_cycle)
+
+for i, sty in enumerate(sty_cycle):
+   ax1.plot(x, x*(i+1), **sty)
+
+sty_cycle = (color_cycle + lw_cycle) * ls_cycle
+
+for i, sty in enumerate(sty_cycle):
+   ax2.plot(x, x*(i+1), **sty)
+
+
+

(Source code, png, hires.png, pdf)

+
+_images/index-2.png +
+
+
+

Persistent Cycles

+

It can be useful to associate a given label with a style via +dictionary lookup and to dynamically generate that mapping. This +can easily be accomplished using a defaultdict

+
In [40]: from cycler import cycler as cy
+
+In [41]: from collections import defaultdict
+
+In [42]: cyl = cy('c', 'rgb') + cy('lw', range(1, 4))
+
+
+

To get a finite set of styles

+
In [43]: finite_cy_iter = iter(cyl)
+
+In [44]: dd_finite = defaultdict(lambda : next(finite_cy_iter))
+
+
+

or repeating

+
In [45]: loop_cy_iter = cyl()
+
+In [46]: dd_loop = defaultdict(lambda : next(loop_cy_iter))
+
+
+

This can be helpful when plotting complex data which has both a classification +and a label

+
finite_cy_iter = iter(cyl)
+styles = defaultdict(lambda : next(finite_cy_iter))
+for group, label, data in DataSet:
+    ax.plot(data, label=label, **styles[group])
+
+
+

which will result in every data with the same group being plotted with +the same style.

+
+
+

Exceptions

+

A ValueError is raised if unequal length Cyclers are added together

+
In [47]: cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])
+---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[47], line 1
+----> 1 cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])
+
+File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:283, in Cycler.__add__(self, other)
+    275 """
+    276 Pair-wise combine two equal length cyclers (zip).
+    277 
+   (...)
+    280 other : Cycler
+    281 """
+    282 if len(self) != len(other):
+--> 283     raise ValueError(
+    284         f"Can only add equal length cycles, not {len(self)} and {len(other)}"
+    285     )
+    286 return Cycler(
+    287     cast(Cycler[Union[K, L], Union[V, U]], self),
+    288     cast(Cycler[Union[K, L], Union[V, U]], other),
+    289     zip
+    290 )
+
+ValueError: Can only add equal length cycles, not 3 and 2
+
+
+

or if two cycles which have overlapping keys are composed

+
In [48]: color_cycle = cycler(c=['r', 'g', 'b'])
+
+In [49]: color_cycle + color_cycle
+---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[49], line 1
+----> 1 color_cycle + color_cycle
+
+File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:286, in Cycler.__add__(self, other)
+    282 if len(self) != len(other):
+    283     raise ValueError(
+    284         f"Can only add equal length cycles, not {len(self)} and {len(other)}"
+    285     )
+--> 286 return Cycler(
+    287     cast(Cycler[Union[K, L], Union[V, U]], self),
+    288     cast(Cycler[Union[K, L], Union[V, U]], other),
+    289     zip
+    290 )
+
+File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:179, in Cycler.__init__(self, left, right, op)
+    176 else:
+    177     self._right = None
+--> 179 self._keys: set[K] = _process_keys(self._left, self._right)
+    180 self._op: Any = op
+
+File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:84, in _process_keys(left, right)
+     82 r_key: set[K] = set(r_peek.keys())
+     83 if l_key & r_key:
+---> 84     raise ValueError("Can not compose overlapping cycles")
+     85 return l_key | r_key
+
+ValueError: Can not compose overlapping cycles
+
+
+
+
+
+

Motivation

+

When plotting more than one line it is common to want to be able to cycle over one +or more artist styles. For simple cases than can be done with out too much trouble:

+
fig, ax = plt.subplots(tight_layout=True)
+x = np.linspace(0, 2*np.pi, 1024)
+
+for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
+   ax.plot(x, np.sin(x - i * np.pi / 4),
+           label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
+           lw=lw + 1,
+           c=c)
+
+ax.set_xlim([0, 2*np.pi])
+ax.set_title(r'$y=\sin(\theta + \phi)$')
+ax.set_ylabel(r'[arb]')
+ax.set_xlabel(r'$\theta$ [rad]')
+
+ax.legend(loc=0)
+
+
+

(Source code, png, hires.png, pdf)

+
+_images/index-3.png +
+

However, if you want to do something more complicated:

+
fig, ax = plt.subplots(tight_layout=True)
+x = np.linspace(0, 2*np.pi, 1024)
+
+for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
+   if i % 2:
+       ls = '-'
+   else:
+       ls = '--'
+   ax.plot(x, np.sin(x - i * np.pi / 4),
+           label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
+           lw=lw + 1,
+           c=c,
+           ls=ls)
+
+ax.set_xlim([0, 2*np.pi])
+ax.set_title(r'$y=\sin(\theta + \phi)$')
+ax.set_ylabel(r'[arb]')
+ax.set_xlabel(r'$\theta$ [rad]')
+
+ax.legend(loc=0)
+
+
+

(Source code, png, hires.png, pdf)

+
+_images/index-4.png +
+

the plotting logic can quickly become very involved. To address this and allow +easy cycling over arbitrary kwargs the Cycler class, a composable keyword +argument iterator, was developed.

+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000..0152a99 --- /dev/null +++ b/objects.inv @@ -0,0 +1,7 @@ +# Sphinx inventory version 2 +# Project: cycler +# Version: 0.12 +# The remainder of this file is compressed using zlib. +xڥj0E +AuBfE!hbꅤ@<&vIs(.mA'mР#6j)ˌI##cBclxQ'iZn\lx\bw:1nR;%Oq?t65<ߞ,gD4 + OO:kC?o{lpkTPľ%˖jgf} ڊ'^k#_V;(ou{Crm Zf _2r}0 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index c7c79cb..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,52 +0,0 @@ -[project] -name = "cycler" -dynamic = ["version"] -description = "Composable style cycles" -authors = [ - {name = "Thomas A Caswell", email = "matplotlib-users@python.org"}, -] -readme = "README.rst" -license = {file = "LICENSE"} -requires-python = ">=3.8" -classifiers = [ - "License :: OSI Approved :: BSD License", - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3 :: Only", -] -keywords = ["cycle kwargs"] - -[project.urls] -homepage = "https://matplotlib.org/cycler/" -repository = "https://github.com/matplotlib/cycler" - -[project.optional-dependencies] -docs = [ - "ipython", - "matplotlib", - "numpydoc", - "sphinx", -] -tests = [ - "pytest", - "pytest-cov", - "pytest-xdist", -] - -[tool.setuptools] -packages = ["cycler"] - -[tool.setuptools.dynamic] -version = {attr = "cycler.__version__"} - -[tool.setuptools.package-data] -cycler = ["py.typed"] - -[build-system] -requires = ["setuptools>=61"] -build-backend = "setuptools.build_meta" diff --git a/search.html b/search.html new file mode 100644 index 0000000..a0bfd0c --- /dev/null +++ b/search.html @@ -0,0 +1,124 @@ + + + + + + + Search — cycler 0.12.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + + +
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..deea112 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["generated/cycler.Cycler", "generated/cycler.concat", "generated/cycler.cycler", "index"], "filenames": ["generated/cycler.Cycler.rst", "generated/cycler.concat.rst", "generated/cycler.cycler.rst", "index.rst"], "titles": ["cycler.Cycler", "cycler.concat", "cycler.cycler", "Composable cycles"], "terms": {"class": [0, 3], "left": [0, 1, 3], "k": [0, 1, 2, 3], "v": [0, 1, 2, 3], "iter": [0, 2, 3], "dict": [0, 3], "none": [0, 3], "right": [0, 1, 3], "op": [0, 3], "ani": [0, 2, 3], "sourc": [0, 1, 2, 3], "compos": [0, 2], "cycl": [0, 2], "thi": [0, 2, 3], "ha": [0, 2, 3], "composit": 0, "method": [0, 3], "inner": [0, 2, 3], "product": [0, 2, 3], "zip": [0, 2, 3], "place": 0, "outer": [0, 3], "itertool": [0, 1, 3], "integ": [0, 2], "multipl": 0, "support": 0, "basic": 0, "slice": 0, "via": [0, 3], "paramet": [0, 2], "The": [0, 1, 2, 3], "func": 0, "function": [0, 2, 3], "which": [0, 3], "__init__": [0, 3], "semi": 0, "privat": 0, "init": 0, "do": [0, 3], "us": [0, 1, 2, 3], "directli": 0, "instead": 0, "attribut": 0, "by_kei": [0, 3], "list": [0, 3], "valu": [0, 2, 3], "kei": [0, 1, 2, 3], "return": [0, 1, 2, 3], "transpos": [0, 3], "over": [0, 3], "yield": [0, 3], "singl": [0, 2, 3], "each": 0, "ar": [0, 2, 3], "given": [0, 2, 3], "can": [0, 2, 3], "creat": [0, 2, 3], "an": [0, 2, 3], "equival": 0, "onli": [0, 3], "change_kei": 0, "old": 0, "new": [0, 2, 3], "chang": [0, 2], "name": [0, 2], "modif": 0, "i": [0, 2, 3], "perform": 0, "doe": [0, 2], "noth": 0, "same": [0, 3], "rais": [0, 3], "valueerror": [0, 3], "alreadi": 0, "keyerror": 0, "isn": 0, "t": 0, "concat": [0, 3], "u": [0, 1, 3], "concaten": [0, 1], "": [0, 1, 3], "chain": [0, 1], "must": [0, 1, 2, 3], "match": [0, 1], "exactli": [0, 1], "exampl": [0, 1], "num": [0, 1], "rang": [0, 1, 3], "3": [0, 1, 2, 3], "let": [0, 1], "abc": [0, 1], "0": [0, 1, 3], "1": [0, 1, 2, 3], "2": [0, 1, 2, 3], "b": [0, 1, 3], "c": [0, 1, 3], "properti": [0, 2], "set": [0, 3], "know": [0, 3], "about": 0, "simplifi": 0, "sum": 0, "simpl": [0, 3], "arg": 2, "kwarg": [2, 3], "str": 2, "label": [2, 3], "itr": 2, "object": [2, 3], "from": [2, 3], "posit": 2, "argument": [2, 3], "pair": [2, 3], "combin": [2, 3], "keyword": [2, 3], "label1": 2, "itr1": 2, "label2": 2, "iter2": 2, "form": 2, "simpli": 2, "copi": 2, "In": [2, 3], "other": [2, 3], "word": 2, "all": 2, "simultan": 2, "through": 2, "when": [2, 3], "cannot": 2, "e": 2, "g": [2, 3], "space": 2, "constructor": 2, "shallow": 2, "hashabl": [2, 3], "valid": 2, "python": [2, 3], "identifi": 2, "finit": [2, 3], "length": [2, 3], "would": 2, "like": [2, 3], "version": 3, "12": 3, "date": 3, "jan": 3, "18": 3, "2024": 3, "doc": 3, "http": 3, "matplotlib": 3, "org": 3, "pypi": 3, "github": 3, "com": 3, "public": 3, "consist": 3, "factori": 3, "provid": 3, "interfac": 3, "while": 3, "take": 3, "care": 3, "logic": 3, "A": 3, "entri": 3, "easili": 3, "style": 3, "To": 3, "link": 3, "seri": 3, "eventu": 3, "__future__": 3, "import": 3, "print_funct": 3, "color_cycl": 3, "color": 3, "r": 3, "4": 3, "out": 3, "its": 3, "5": 3, "len": 3, "6": 3, "7": 3, "print": 3, "pass": 3, "8": 3, "ec": 3, "result": 3, "get": 3, "infinit": 3, "call": 3, "la": 3, "gener": 3, "9": 3, "cc": 3, "10": 3, "j": 3, "just": 3, "replac": 3, "loop": 3, "power": 3, "thei": 3, "complex": 3, "multi": 3, "equal": 3, "differ": 3, "ad": 3, "two": 3, "11": 3, "lw_cycl": 3, "lw": 3, "wc": 3, "union": 3, "input": 3, "13": 3, "14": 3, "15": 3, "As": 3, "arithmet": 3, "commut": 3, "16": 3, "lw_c": 3, "17": 3, "c_lw": 3, "enumer": 3, "format": 3, "For": 3, "conveni": 3, "have": 3, "automat": 3, "them": 3, "19": 3, "20": 3, "multipli": 3, "21": 3, "m_cycl": 3, "marker": 3, "o": 3, "22": 3, "m_c": 3, "give": 3, "23": 3, "24": 3, "25": 3, "note": 3, "unlik": 3, "matric": 3, "26": 3, "c_m": 3, "27": 3, "also": 3, "increas": 3, "28": 3, "29": 3, "either": 3, "30": 3, "top": 3, "level": 3, "31": 3, "32": 3, "33": 3, "34": 3, "35": 3, "sub": 3, "36": 3, "mutat": 3, "updat": 3, "37": 3, "bk": 3, "38": 3, "green": 3, "39": 3, "we": 3, "instanc": 3, "one": 3, "more": 3, "plot": 3, "fig": 3, "ax1": 3, "ax2": 3, "plt": 3, "subplot": 3, "tight_layout": 3, "true": 3, "figsiz": 3, "x": 3, "np": 3, "arang": 3, "sty": 3, "code": 3, "png": 3, "hire": 3, "pdf": 3, "ls_cycl": 3, "l": 3, "sty_cycl": 3, "It": 3, "associ": 3, "dictionari": 3, "lookup": 3, "dynam": 3, "map": 3, "accomplish": 3, "defaultdict": 3, "40": 3, "cy": 3, "41": 3, "collect": 3, "42": 3, "cyl": 3, "rgb": 3, "43": 3, "finite_cy_it": 3, "44": 3, "dd_finit": 3, "lambda": 3, "next": 3, "repeat": 3, "45": 3, "loop_cy_it": 3, "46": 3, "dd_loop": 3, "help": 3, "data": 3, "both": 3, "classif": 3, "group": 3, "dataset": 3, "ax": 3, "everi": 3, "being": 3, "unequ": 3, "togeth": 3, "47": 3, "traceback": 3, "most": 3, "recent": 3, "last": 3, "cell": 3, "line": 3, "file": 3, "venv": 3, "lib64": 3, "python3": 3, "site": 3, "packag": 3, "py": 3, "283": 3, "__add__": 3, "self": 3, "275": 3, "276": 3, "wise": 3, "277": 3, "280": 3, "281": 3, "282": 3, "284": 3, "f": 3, "add": 3, "285": 3, "286": 3, "287": 3, "cast": 3, "288": 3, "289": 3, "290": 3, "overlap": 3, "48": 3, "49": 3, "179": 3, "176": 3, "els": 3, "177": 3, "_right": 3, "_kei": 3, "_process_kei": 3, "_left": 3, "180": 3, "_op": 3, "84": 3, "82": 3, "r_kei": 3, "r_peek": 3, "83": 3, "l_kei": 3, "85": 3, "than": 3, "common": 3, "want": 3, "abl": 3, "artist": 3, "case": 3, "done": 3, "too": 3, "much": 3, "troubl": 3, "linspac": 3, "pi": 3, "1024": 3, "sin": 3, "phi": 3, "set_xlim": 3, "set_titl": 3, "y": 3, "theta": 3, "set_ylabel": 3, "arb": 3, "set_xlabel": 3, "rad": 3, "legend": 3, "loc": 3, "howev": 3, "you": 3, "someth": 3, "complic": 3, "quickli": 3, "becom": 3, "veri": 3, "involv": 3, "address": 3, "allow": 3, "easi": 3, "arbitrari": 3, "wa": 3, "develop": 3}, "objects": {"cycler": [[0, 0, 1, "", "Cycler"], [1, 3, 1, "", "concat"], [2, 3, 1, "", "cycler"]], "cycler.Cycler": [[0, 1, 1, "", "__init__"], [0, 1, 1, "", "by_key"], [0, 1, 1, "", "change_key"], [0, 1, 1, "", "concat"], [0, 2, 1, "", "keys"], [0, 1, 1, "", "simplify"]]}, "objtypes": {"0": "py:class", "1": "py:method", "2": "py:property", "3": "py:function"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"], "2": ["py", "property", "Python property"], "3": ["py", "function", "Python function"]}, "titleterms": {"cycler": [0, 1, 2, 3], "concat": 1, "compos": 3, "cycl": 3, "api": 3, "usag": 3, "base": 3, "composit": 3, "addit": 3, "multipl": 3, "integ": 3, "concaten": 3, "slice": 3, "inspect": 3, "exampl": 3, "persist": 3, "except": 3, "motiv": 3}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"cycler.Cycler": [[0, "cycler-cycler"]], "cycler.concat": [[1, "cycler-concat"]], "cycler.cycler": [[2, "cycler-cycler"]], "Composable cycles": [[3, "composable-cycles"]], "cycler API": [[3, "cycler-api"]], "Cycler Usage": [[3, "cycler-usage"]], "Base": [[3, "base"]], "Composition": [[3, "composition"]], "Addition": [[3, "addition"]], "Multiplication": [[3, "multiplication"]], "Integer Multiplication": [[3, "integer-multiplication"]], "Concatenation": [[3, "concatenation"]], "Slicing": [[3, "slicing"]], "Inspecting the Cycler": [[3, "inspecting-the-cycler"]], "Examples": [[3, "examples"]], "Persistent Cycles": [[3, "persistent-cycles"]], "Exceptions": [[3, "exceptions"]], "Motivation": [[3, "motivation"]]}, "indexentries": {"cycler (class in cycler)": [[0, "cycler.Cycler"]], "__init__() (cycler.cycler method)": [[0, "cycler.Cycler.__init__"]], "by_key() (cycler.cycler method)": [[0, "cycler.Cycler.by_key"]], "change_key() (cycler.cycler method)": [[0, "cycler.Cycler.change_key"]], "concat() (cycler.cycler method)": [[0, "cycler.Cycler.concat"]], "keys (cycler.cycler property)": [[0, "cycler.Cycler.keys"]], "simplify() (cycler.cycler method)": [[0, "cycler.Cycler.simplify"]], "concat() (in module cycler)": [[1, "cycler.concat"]], "cycler() (in module cycler)": [[2, "cycler.cycler"]]}}) \ No newline at end of file diff --git a/test_cycler.py b/test_cycler.py deleted file mode 100644 index 1eccac3..0000000 --- a/test_cycler.py +++ /dev/null @@ -1,364 +0,0 @@ -from collections import defaultdict -from operator import add, iadd, mul, imul -from itertools import product, cycle, chain - -import pytest # type: ignore - -from cycler import cycler, Cycler, concat - - -def _cycler_helper(c, length, keys, values): - assert len(c) == length - assert len(c) == len(list(c)) - assert c.keys == set(keys) - for k, vals in zip(keys, values): - for v, v_target in zip(c, vals): - assert v[k] == v_target - - -def _cycles_equal(c1, c2): - assert list(c1) == list(c2) - assert c1 == c2 - - -@pytest.mark.parametrize('c', [cycler(c='rgb'), - cycler(c=list('rgb')), - cycler(cycler(c='rgb'))], - ids=['from string', - 'from list', - 'from cycler']) -def test_creation(c): - _cycler_helper(c, 3, ['c'], [['r', 'g', 'b']]) - - -def test_add(): - c1 = cycler(c='rgb') - c2 = cycler(lw=range(3)) - # addition - _cycler_helper(c1 + c2, 3, ['c', 'lw'], [list('rgb'), range(3)]) - _cycler_helper(c2 + c1, 3, ['c', 'lw'], [list('rgb'), range(3)]) - _cycles_equal(c2 + c1, c1 + c2) - - -def test_add_len_mismatch(): - # miss-matched add lengths - c1 = cycler(c='rgb') - c3 = cycler(lw=range(15)) - with pytest.raises(ValueError): - c1 + c3 - with pytest.raises(ValueError): - c3 + c1 - - -def test_prod(): - c1 = cycler(c='rgb') - c2 = cycler(lw=range(3)) - c3 = cycler(lw=range(15)) - # multiplication - target = zip(*product(list('rgb'), range(3))) - _cycler_helper(c1 * c2, 9, ['c', 'lw'], target) - - target = zip(*product(range(3), list('rgb'))) - _cycler_helper(c2 * c1, 9, ['lw', 'c'], target) - - target = zip(*product(range(15), list('rgb'))) - _cycler_helper(c3 * c1, 45, ['lw', 'c'], target) - - -def test_inplace_add(): - c1 = cycler(c='rgb') - c2 = cycler(lw=range(3)) - c2 += c1 - _cycler_helper(c2, 3, ['c', 'lw'], [list('rgb'), range(3)]) - - -def test_inplace_add_len_mismatch(): - # miss-matched add lengths - c1 = cycler(c='rgb') - c3 = cycler(lw=range(15)) - with pytest.raises(ValueError): - c1 += c3 - - -def test_inplace_mul(): - c3 = cycler(c='rgb') - c4 = cycler(lw=range(3)) - c3 *= c4 - target = zip(*product(list('rgb'), range(3))) - _cycler_helper(c3, 9, ['c', 'lw'], target) - - -def test_constructor(): - c1 = cycler(c='rgb') - c2 = cycler(ec=c1) - _cycler_helper(c1 + c2, 3, ['c', 'ec'], [['r', 'g', 'b']] * 2) - c3 = cycler(c=c1) - _cycler_helper(c3 + c2, 3, ['c', 'ec'], [['r', 'g', 'b']] * 2) - # Using a non-string hashable - c4 = cycler(1, range(3)) - _cycler_helper(c4 + c1, 3, [1, 'c'], [range(3), ['r', 'g', 'b']]) - - # addition using cycler() - _cycler_helper(cycler(c='rgb', lw=range(3)), - 3, ['c', 'lw'], [list('rgb'), range(3)]) - _cycler_helper(cycler(lw=range(3), c='rgb'), - 3, ['c', 'lw'], [list('rgb'), range(3)]) - # Purposely mixing them - _cycler_helper(cycler(c=range(3), lw=c1), - 3, ['c', 'lw'], [range(3), list('rgb')]) - - -def test_failures(): - c1 = cycler(c='rgb') - c2 = cycler(c=c1) - pytest.raises(ValueError, add, c1, c2) - pytest.raises(ValueError, iadd, c1, c2) - pytest.raises(ValueError, mul, c1, c2) - pytest.raises(ValueError, imul, c1, c2) - pytest.raises(TypeError, iadd, c2, 'aardvark') - pytest.raises(TypeError, imul, c2, 'aardvark') - - c3 = cycler(ec=c1) - - pytest.raises(ValueError, cycler, c=c2 + c3) - - -def test_simplify(): - c1 = cycler(c='rgb') - c2 = cycler(ec=c1) - for c in [c1 * c2, c2 * c1, c1 + c2]: - _cycles_equal(c, c.simplify()) - - -def test_multiply(): - c1 = cycler(c='rgb') - _cycler_helper(2 * c1, 6, ['c'], ['rgb' * 2]) - - c2 = cycler(ec=c1) - c3 = c1 * c2 - - _cycles_equal(2 * c3, c3 * 2) - - -def test_mul_fails(): - c1 = cycler(c='rgb') - pytest.raises(TypeError, mul, c1, 2.0) - pytest.raises(TypeError, mul, c1, 'a') - pytest.raises(TypeError, mul, c1, []) - - -def test_getitem(): - c1 = cycler(3, range(15)) - widths = list(range(15)) - for slc in (slice(None, None, None), - slice(None, None, -1), - slice(1, 5, None), - slice(0, 5, 2)): - _cycles_equal(c1[slc], cycler(3, widths[slc])) - - -def test_fail_getime(): - c1 = cycler(lw=range(15)) - pytest.raises(ValueError, Cycler.__getitem__, c1, 0) - pytest.raises(ValueError, Cycler.__getitem__, c1, [0, 1]) - - -def test_repr(): - c = cycler(c='rgb') - # Using an identifier that would be not valid as a kwarg - c2 = cycler('3rd', range(3)) - - assert repr(c + c2) == ( - "(cycler('c', ['r', 'g', 'b']) + cycler('3rd', [0, 1, 2]))") - assert repr(c * c2) == ( - "(cycler('c', ['r', 'g', 'b']) * cycler('3rd', [0, 1, 2]))") - - assert (c + c2)._repr_html_() == ( - "" - "" - "" - "" - "" - "
'3rd''c'
0'r'
1'g'
2'b'
") - assert (c * c2)._repr_html_() == ( - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "
'3rd''c'
0'r'
1'r'
2'r'
0'g'
1'g'
2'g'
0'b'
1'b'
2'b'
") - - -def test_call(): - c = cycler(c='rgb') - c_cycle = c() - assert isinstance(c_cycle, cycle) - j = 0 - for a, b in zip(2 * c, c_cycle): - j += 1 - assert a == b - - assert j == len(c) * 2 - - -def test_copying(): - # Just about everything results in copying the cycler and - # its contents (shallow). This set of tests is intended to make sure - # of that. Our iterables will be mutable for extra fun! - i1 = [1, 2, 3] - i2 = ['r', 'g', 'b'] - # For more mutation fun! - i3 = [['y', 'g'], ['b', 'k']] - - c1 = cycler('c', i1) - c2 = cycler('lw', i2) - c3 = cycler('foo', i3) - - c_before = (c1 + c2) * c3 - - i1.pop() - i2.append('cyan') - i3[0].append('blue') - - c_after = (c1 + c2) * c3 - - assert c1 == cycler('c', [1, 2, 3]) - assert c2 == cycler('lw', ['r', 'g', 'b']) - assert c3 == cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]) - assert c_before == (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) * - cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])) - assert c_after == (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) * - cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])) - - # Make sure that changing the key for a specific cycler - # doesn't break things for a composed cycler - c = (c1 + c2) * c3 - c4 = cycler('bar', c3) - assert c == (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) * - cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])) - assert c3 == cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]) - assert c4 == cycler('bar', [['y', 'g', 'blue'], ['b', 'k']]) - - -def test_keychange(): - c1 = cycler('c', 'rgb') - c2 = cycler('lw', [1, 2, 3]) - c3 = cycler('ec', 'yk') - - c3.change_key('ec', 'edgecolor') - assert c3 == cycler('edgecolor', c3) - - c = c1 + c2 - c.change_key('lw', 'linewidth') - # Changing a key in one cycler should have no - # impact in the original cycler. - assert c2 == cycler('lw', [1, 2, 3]) - assert c == c1 + cycler('linewidth', c2) - - c = (c1 + c2) * c3 - c.change_key('c', 'color') - assert c1 == cycler('c', 'rgb') - assert c == (cycler('color', c1) + c2) * c3 - - # Perfectly fine, it is a no-op - c.change_key('color', 'color') - assert c == (cycler('color', c1) + c2) * c3 - - # Can't change a key to one that is already in there - pytest.raises(ValueError, Cycler.change_key, c, 'color', 'lw') - # Can't change a key you don't have - pytest.raises(KeyError, Cycler.change_key, c, 'c', 'foobar') - - -def test_eq(): - a = cycler(c='rgb') - b = cycler(c='rgb') - assert a == b - assert a != b[::-1] - c = cycler(lw=range(3)) - assert a + c == c + a - assert a + c == c + b - assert a * c != c * a - assert a != c - d = cycler(c='ymk') - assert b != d - e = cycler(c='orange') - assert b != e - - -def test_cycler_exceptions(): - pytest.raises(TypeError, cycler) - pytest.raises(TypeError, cycler, 'c', 'rgb', lw=range(3)) - pytest.raises(TypeError, cycler, 'c') - pytest.raises(TypeError, cycler, 'c', 'rgb', 'lw', range(3)) - - -def test_strange_init(): - c = cycler('r', 'rgb') - c2 = cycler('lw', range(3)) - cy = Cycler(list(c), c2, zip) - assert cy == c + c2 - - -def test_concat(): - a = cycler('a', range(3)) - b = cycler('a', 'abc') - for con, chn in zip(a.concat(b), chain(a, b)): - assert con == chn - - for con, chn in zip(concat(a, b), chain(a, b)): - assert con == chn - - -def test_concat_fail(): - a = cycler('a', range(3)) - b = cycler('b', range(3)) - pytest.raises(ValueError, concat, a, b) - pytest.raises(ValueError, a.concat, b) - - -def _by_key_helper(cy): - res = cy.by_key() - target = defaultdict(list) - for sty in cy: - for k, v in sty.items(): - target[k].append(v) - - assert res == target - - -def test_by_key_add(): - input_dict = dict(c=list('rgb'), lw=[1, 2, 3]) - cy = cycler(c=input_dict['c']) + cycler(lw=input_dict['lw']) - res = cy.by_key() - assert res == input_dict - _by_key_helper(cy) - - -def test_by_key_mul(): - input_dict = dict(c=list('rg'), lw=[1, 2, 3]) - cy = cycler(c=input_dict['c']) * cycler(lw=input_dict['lw']) - res = cy.by_key() - assert input_dict['lw'] * len(input_dict['c']) == res['lw'] - _by_key_helper(cy) - - -def test_contains(): - a = cycler('a', range(3)) - b = cycler('b', range(3)) - - assert 'a' in a - assert 'b' in b - assert 'a' not in b - assert 'b' not in a - - ab = a + b - - assert 'a' in ab - assert 'b' in ab