diff --git a/.bumpversion.cfg b/.bumpversion.cfg index dc97cac..5de3fdf 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,13 +1,14 @@ [bumpversion] -current_version = 0.1.0 +current_version = 0.3.0 commit = True tag = True [bumpversion:file:pyproject.toml] -[bumpversion:file:repo_helper.yml] +search = version = "{current_version}" +replace = version = "{new_version}" -[bumpversion:file:__pkginfo__.py] +[bumpversion:file:repo_helper.yml] [bumpversion:file:README.rst] @@ -18,3 +19,7 @@ tag = True [bumpversion:file:sphinx_pyproject/__init__.py] search = : str = "{current_version}" replace = : str = "{new_version}" + +[bumpversion:file:.github/workflows/conda_ci.yml] +search = ={current_version}=py_1 +replace = ={new_version}=py_1 diff --git a/.dependabot/config.yml b/.dependabot/config.yml deleted file mode 100644 index 4584924..0000000 --- a/.dependabot/config.yml +++ /dev/null @@ -1,9 +0,0 @@ -# This file is managed by 'repo_helper'. Don't edit it directly. ---- -version: 1 -update_configs: -- package_manager: python - directory: / - update_schedule: weekly - default_reviewers: - - domdfcoding diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1026c30..31229d6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -44,7 +44,7 @@ If possible, please include a small, self-contained reproduction. * sphinx-pyproject: ## Installation source - + ## Other Additional Information: diff --git a/.github/actions_build_conda.sh b/.github/actions_build_conda.sh deleted file mode 100755 index 4983f89..0000000 --- a/.github/actions_build_conda.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# This file is managed by 'repo_helper'. Don't edit it directly. - -set -e -x - -python -m mkrecipe || exit 1 - -# Switch to miniconda -source "/home/runner/miniconda/etc/profile.d/conda.sh" -hash -r -conda activate base -conda config --set always_yes yes --set changeps1 no -conda update -q conda -conda install conda-build -conda install anaconda-client -conda info -a - -conda config --add channels conda-forge || exit 1 -conda config --add channels domdfcoding || exit 1 - -conda build conda -c conda-forge -c domdfcoding --output-folder conda/dist --skip-existing - -exit 0 diff --git a/.github/actions_deploy_conda.sh b/.github/actions_deploy_conda.sh deleted file mode 100755 index 0603f84..0000000 --- a/.github/actions_deploy_conda.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# This file is managed by 'repo_helper'. Don't edit it directly. - -set -e -x - -# Switch to miniconda -source "/home/runner/miniconda/etc/profile.d/conda.sh" -hash -r -conda activate base -conda config --set always_yes yes --set changeps1 no -conda update -q conda -conda install anaconda-client -conda info -a - -for f in conda/dist/noarch/sphinx-pyproject-*.tar.bz2; do - [ -e "$f" ] || continue - echo "$f" - conda install "$f" || exit 1 - echo "Deploying to Anaconda.org..." - anaconda -t "$ANACONDA_TOKEN" upload "$f" || exit 1 - echo "Successfully deployed to Anaconda.org." -done - -exit 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e769ad3..454225a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,5 +6,6 @@ updates: directory: / schedule: interval: weekly + open-pull-requests-limit: 0 reviewers: - domdfcoding diff --git a/.github/milestones.py b/.github/milestones.py new file mode 100755 index 0000000..5e868dc --- /dev/null +++ b/.github/milestones.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# stdlib +import os +import sys + +# 3rd party +from github3 import GitHub +from github3.repos import Repository +from packaging.version import InvalidVersion, Version + +latest_tag = os.environ["GITHUB_REF_NAME"] + +try: + current_version = Version(latest_tag) +except InvalidVersion: + sys.exit() + +gh: GitHub = GitHub(token=os.environ["GITHUB_TOKEN"]) +repo: Repository = gh.repository(*os.environ["GITHUB_REPOSITORY"].split('/', 1)) + +for milestone in repo.milestones(state="open"): + try: + milestone_version = Version(milestone.title) + except InvalidVersion: + continue + if milestone_version == current_version: + sys.exit(not milestone.update(state="closed")) diff --git a/.github/stale.yml b/.github/stale.yml index bb7ca3f..bb7fa62 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -7,7 +7,7 @@ daysUntilStale: 180 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 180 +daysUntilClose: false # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) onlyLabels: [] @@ -28,13 +28,13 @@ exemptMilestones: false exemptAssignees: false # Label to use when marking as stale -staleLabel: wontfix +staleLabel: stale # Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. +markComment: false +# This issue has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. # Comment to post when removing the stale label. # unmarkComment: > diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml deleted file mode 100644 index 741c0bd..0000000 --- a/.github/workflows/cleanup.yml +++ /dev/null @@ -1,14 +0,0 @@ -# This file is managed by 'repo_helper'. Don't edit it directly. ---- -name: Artefact Cleaner -on: - schedule: - - cron: 0 9 1 * * -jobs: - Clean: - runs-on: ubuntu-latest - steps: - - name: cleanup - uses: glassechidna/artifact-cleaner@v2 - with: - minimumAge: 1000000.0 diff --git a/.github/workflows/conda_ci.yml b/.github/workflows/conda_ci.yml index c67b9e5..8f29453 100644 --- a/.github/workflows/conda_ci.yml +++ b/.github/workflows/conda_ci.yml @@ -6,38 +6,63 @@ on: push: branches: ["master"] +permissions: + contents: read + jobs: tests: name: "Conda" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash -l {0} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2.1.1 with: - python-version: "3.8" + activate-environment: env + conda-build-version: 3.28.4 + miniconda-version: py311_24.1.2-0 + python-version: "3.11" + miniforge-variant: Mambaforge - name: Install dependencies 🔧 run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade repo_helper + python -m pip install --upgrade "whey-conda" "whey" # $CONDA is an environment variable pointing to the root of the miniconda directory - $CONDA/bin/conda update -q conda - $CONDA/bin/conda install conda-build=3.21.0 - + $CONDA/bin/conda update -n base conda $CONDA/bin/conda config --add channels conda-forge $CONDA/bin/conda config --add channels domdfcoding - - name: "Build and install package" + - name: "Build and index channel" run: | - # This mess is only necessary because conda won't fix it themselves - # https://github.com/conda/conda/issues/1884 - - python -m repo_helper build --conda --out-dir conda-bld/noarch + python -m whey --builder whey_conda --out-dir conda-bld/noarch $CONDA/bin/conda index ./conda-bld || exit 1 - $CONDA/bin/conda install -c file://$(pwd)/conda-bld sphinx-pyproject -y || exit 1 + + - name: "Search for package" + run: | + $CONDA/bin/conda search -c file://$(pwd)/conda-bld sphinx-pyproject + $CONDA/bin/conda search -c file://$(pwd)/conda-bld --override-channels sphinx-pyproject + + - name: "Install package" + run: | + $CONDA/bin/conda install -c file://$(pwd)/conda-bld sphinx-pyproject=0.3.0=py_1 -y || exit 1 + + - name: "Run Tests" + run: | + rm -rf sphinx_pyproject + $CONDA/bin/conda install pytest coincidence || exit 1 + pip install -r tests/requirements.txt + pytest tests/ diff --git a/.github/workflows/docs_test_action.yml b/.github/workflows/docs_test_action.yml index 51d1d73..8f60ba5 100644 --- a/.github/workflows/docs_test_action.yml +++ b/.github/workflows/docs_test_action.yml @@ -2,17 +2,36 @@ --- name: "Docs Check" on: - - push + push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + pull_request: + +permissions: + contents: read jobs: docs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: "actions/checkout@v1" + uses: "actions/checkout@v4" + + - name: Check for changed files + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!tests/**' + - name: Install and Build 🔧 - uses: ammaraskar/sphinx-action@master + uses: sphinx-toolbox/sphinx-action@sphinx-3.3.1 + if: steps.changes.outputs.code == 'true' with: - pre-build-command: apt-get update && apt-get install gcc python3-dev git pandoc -y && python -m pip install tox + pre-build-command: python -m pip install tox docs-folder: "doc-source/" - build-command: "tox -e docs -- " + build-command: "tox -e docs -- -W " diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index 29303d6..5e67c5c 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -4,27 +4,47 @@ name: Flake8 on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + pull_request: + +permissions: + contents: read jobs: Run: name: "Flake8" - runs-on: "ubuntu-18.04" + runs-on: "ubuntu-22.04" steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + if: steps.changes.outputs.code == 'true' + uses: "actions/setup-python@v5" with: - python-version: "3.8" + python-version: "3.9" - name: Install dependencies 🔧 + if: steps.changes.outputs.code == 'true' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install tox + python -m pip install tox~=3.0 - name: "Run Flake8" - run: "python -m tox -e lint -- --format github" + if: steps.changes.outputs.code == 'true' + run: "python -m tox -e lint -s false -- --format github" diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index e10cf9b..4c22a52 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -4,6 +4,14 @@ name: mypy on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + pull_request: + +permissions: + contents: read jobs: Run: @@ -12,24 +20,35 @@ jobs: strategy: matrix: - os: ['ubuntu-20.04', 'windows-2019'] + os: ['ubuntu-22.04', 'windows-2022'] fail-fast: false steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + if: steps.changes.outputs.code == 'true' + uses: "actions/setup-python@v5" with: - python-version: "3.6" + python-version: "3.9" - name: Install dependencies 🔧 run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 - name: "Run mypy" - run: "python -m tox -e mypy" + if: steps.changes.outputs.code == 'true' + run: "python -m tox -e mypy -s false" diff --git a/.github/workflows/octocheese.yml b/.github/workflows/octocheese.yml index c03da28..39d8f6d 100644 --- a/.github/workflows/octocheese.yml +++ b/.github/workflows/octocheese.yml @@ -3,10 +3,8 @@ name: "GitHub Releases" on: - push: - branches: ["master"] schedule: - - cron: 0 12 * * 2,4,6 + - cron: 0 12 * * * jobs: Run: diff --git a/.github/workflows/python_ci.yml b/.github/workflows/python_ci.yml index 0c1bdda..60eebbf 100644 --- a/.github/workflows/python_ci.yml +++ b/.github/workflows/python_ci.yml @@ -4,49 +4,78 @@ name: Windows on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + + pull_request: + +permissions: + actions: write + issues: write + contents: read jobs: tests: - name: "windows-2019 / Python ${{ matrix.config.python-version }}" - runs-on: "windows-2019" + name: "windows-2022 / Python ${{ matrix.config.python-version }}" + runs-on: "windows-2022" continue-on-error: ${{ matrix.config.experimental }} env: - USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10.0-alpha.6,pypy-3.6,pypy-3.7' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - - {python-version: "3.10.0-alpha.6", testenvs: "py310-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} + - {python-version: "3.10", testenvs: "py310,build", experimental: False} + - {python-version: "3.11", testenvs: "py311,build", experimental: False} + - {python-version: "3.12", testenvs: "py312,build", experimental: False} + - {python-version: "3.13", testenvs: "py313,build", experimental: False} + - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: False} + - {python-version: "pypy-3.9-v7.3.15", testenvs: "pypy39,build", experimental: True} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + if: startsWith(github.ref, 'refs/tags/') != true + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + id: setup-python + if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} + uses: "actions/setup-python@v5" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies 🔧 + if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 - name: "Run Tests for Python ${{ matrix.config.python-version }}" - run: python -m tox -e "${{ matrix.config.testenvs }}" + if: steps.setup-python.outcome == 'success' + run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage 🚀" - uses: actions/upload-artifact@v2 - if: ${{ always() }} + uses: actions/upload-artifact@v4 + if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true diff --git a/.github/workflows/python_ci_linux.yml b/.github/workflows/python_ci_linux.yml index 6c82ed2..6f7b37b 100644 --- a/.github/workflows/python_ci_linux.yml +++ b/.github/workflows/python_ci_linux.yml @@ -4,64 +4,94 @@ name: Linux on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + tags: + - '*' + pull_request: + +permissions: + actions: write + issues: write + contents: read jobs: tests: - name: "ubuntu-20.04 / Python ${{ matrix.config.python-version }}" - runs-on: "ubuntu-20.04" + name: "ubuntu-22.04 / Python ${{ matrix.config.python-version }}" + runs-on: "ubuntu-22.04" continue-on-error: ${{ matrix.config.experimental }} env: - USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10.0-alpha.6,pypy-3.6,pypy-3.7' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - - {python-version: "3.10.0-alpha.6", testenvs: "py310-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} + - {python-version: "3.10", testenvs: "py310,build", experimental: False} + - {python-version: "3.11", testenvs: "py311,build", experimental: False} + - {python-version: "3.12", testenvs: "py312,build", experimental: False} + - {python-version: "3.13", testenvs: "py313,build", experimental: False} + - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: False} + - {python-version: "pypy-3.9", testenvs: "pypy39,build", experimental: True} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + if: startsWith(github.ref, 'refs/tags/') != true + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + id: setup-python + if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} + uses: "actions/setup-python@v5" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies 🔧 + if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 python -m pip install --upgrade coverage_pyver_pragma - name: "Run Tests for Python ${{ matrix.config.python-version }}" - run: python -m tox -e "${{ matrix.config.testenvs }}" + if: steps.setup-python.outcome == 'success' + run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage 🚀" - uses: actions/upload-artifact@v2 - if: ${{ always() }} + uses: actions/upload-artifact@v4 + if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true Coverage: needs: tests - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" with: python-version: 3.8 @@ -71,26 +101,32 @@ jobs: python -m pip install --upgrade "coveralls>=3.0.0" coverage_pyver_pragma - name: "Download Coverage 🪂" - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: path: coverage - name: Display structure of downloaded files + id: show run: ls -R working-directory: coverage + continue-on-error: true - name: Combine Coverage 👷 + if: ${{ steps.show.outcome != 'failure' }} run: | shopt -s globstar python -m coverage combine coverage/**/.coverage - name: "Upload Combined Coverage Artefact 🚀" - uses: actions/upload-artifact@v2 + if: ${{ steps.show.outcome != 'failure' }} + uses: actions/upload-artifact@v4 with: name: "combined-coverage" path: .coverage + include-hidden-files: true - name: "Upload Combined Coverage to Coveralls" + if: ${{ steps.show.outcome != 'failure' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -99,29 +135,29 @@ jobs: Deploy: needs: tests - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" if: startsWith(github.ref, 'refs/tags/') - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" + if: startsWith(github.ref, 'refs/tags/') with: python-version: 3.8 - if: startsWith(github.ref, 'refs/tags/') - name: Install dependencies 🔧 + if: startsWith(github.ref, 'refs/tags/') run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox - if: startsWith(github.ref, 'refs/tags/') + python -m pip install --upgrade tox~=3.0 - name: Build distributions 📦 + if: startsWith(github.ref, 'refs/tags/') run: | tox -e build - if: startsWith(github.ref, 'refs/tags/') - name: Upload distribution to PyPI 🚀 if: startsWith(github.ref, 'refs/tags/') @@ -131,37 +167,73 @@ jobs: password: ${{ secrets.PYPI_TOKEN }} skip_existing: true + - name: Close milestone 🚪 + if: startsWith(github.ref, 'refs/tags/') + run: | + python -m pip install --upgrade github3.py packaging + python .github/milestones.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Conda: needs: deploy - runs-on: "ubuntu-20.04" + runs-on: ubuntu-22.04 if: startsWith(github.ref, 'refs/tags/') || (startsWith(github.event.head_commit.message, 'Bump version') != true) steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" with: - python-version: 3.8 + python-version: 3.11 + + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2.1.1 + with: + activate-environment: env + conda-build-version: 3.28.4 + miniconda-version: py311_24.1.2-0 + python-version: "3.11" + miniforge-variant: Mambaforge - name: Install dependencies 🔧 run: | + python -VV + python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade mkrecipe - - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - - - name: Build Conda 📦 + python -m pip install --upgrade "mkrecipe" "whey" + # $CONDA is an environment variable pointing to the root of the miniconda directory + $CONDA/bin/conda config --set always_yes yes --set changeps1 no + $CONDA/bin/conda update -n base conda + $CONDA/bin/conda info -a + $CONDA/bin/conda install conda-forge::py-lief=0.14.1 + $CONDA/bin/conda config --add channels conda-forge + $CONDA/bin/conda config --add channels domdfcoding + + $CONDA/bin/conda config --remove channels defaults + + - name: Build Conda Package 📦 run: | - chmod +x .github/actions_build_conda.sh - bash .github/actions_build_conda.sh + python -m mkrecipe --type wheel || exit 1 + $CONDA/bin/conda build conda -c conda-forge -c domdfcoding --output-folder conda/dist - - name: Deploy Conda 🚀 + - name: Deploy Conda Package 🚀 + if: startsWith(github.ref, 'refs/tags/') run: | - chmod +x .github/actions_deploy_conda.sh - bash .github/actions_deploy_conda.sh + $CONDA/bin/conda config --set always_yes yes --set changeps1 no + $CONDA/bin/conda install anaconda-client + $CONDA/bin/conda info -a + + for f in conda/dist/noarch/sphinx-pyproject-*.tar.bz2; do + [ -e "$f" ] || continue + echo "$f" + conda install "$f" || exit 1 + echo "Deploying to Anaconda.org..." + $CONDA/bin/anaconda -t "$ANACONDA_TOKEN" upload "$f" || exit 1 + echo "Successfully deployed to Anaconda.org." + done env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} diff --git a/.github/workflows/python_ci_macos.yml b/.github/workflows/python_ci_macos.yml index e9e2bc1..9ced940 100644 --- a/.github/workflows/python_ci_macos.yml +++ b/.github/workflows/python_ci_macos.yml @@ -4,49 +4,78 @@ name: macOS on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + + pull_request: + +permissions: + actions: write + issues: write + contents: read jobs: tests: - name: "macos-latest / Python ${{ matrix.config.python-version }}" - runs-on: "macos-latest" + name: "macos-${{ matrix.config.os-ver }} / Python ${{ matrix.config.python-version }}" + runs-on: "macos-${{ matrix.config.os-ver }}" continue-on-error: ${{ matrix.config.experimental }} env: - USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10.0-alpha.6,pypy-3.6,pypy-3.7' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - - {python-version: "3.6", testenvs: "py36,build", experimental: False} - - {python-version: "3.7", testenvs: "py37,build", experimental: False} - - {python-version: "3.8", testenvs: "py38,build", experimental: False} - - {python-version: "3.9", testenvs: "py39,build", experimental: False} - - {python-version: "3.10.0-alpha.6", testenvs: "py310-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} + - {python-version: "3.7", os-ver: "13", testenvs: "py37,build", experimental: False} + - {python-version: "3.8", os-ver: "14", testenvs: "py38,build", experimental: False} + - {python-version: "3.9", os-ver: "14", testenvs: "py39,build", experimental: False} + - {python-version: "3.10", os-ver: "14", testenvs: "py310,build", experimental: False} + - {python-version: "3.11", os-ver: "14", testenvs: "py311,build", experimental: False} + - {python-version: "3.12", os-ver: "14", testenvs: "py312,build", experimental: False} + - {python-version: "3.13", os-ver: "14", testenvs: "py313,build", experimental: False} + - {python-version: "pypy-3.7", os-ver: "13", testenvs: "pypy37,build", experimental: False} + - {python-version: "pypy-3.8", os-ver: "14", testenvs: "pypy38,build", experimental: False} + - {python-version: "pypy-3.9", os-ver: "14", testenvs: "pypy39,build", experimental: True} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + if: startsWith(github.ref, 'refs/tags/') != true + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + id: setup-python + if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} + uses: "actions/setup-python@v5" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies 🔧 + if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 - name: "Run Tests for Python ${{ matrix.config.python-version }}" - run: python -m tox -e "${{ matrix.config.testenvs }}" + if: steps.setup-python.outcome == 'success' + run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage 🚀" - uses: actions/upload-artifact@v2 - if: ${{ always() }} + uses: actions/upload-artifact@v4 + if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dda1963..fe8f96f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,15 @@ exclude: ^$ +ci: + autoupdate_schedule: quarterly + repos: + - repo: https://github.com/repo-helper/pyproject-parser + rev: v0.13.0 + hooks: + - id: reformat-pyproject + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 hooks: @@ -25,28 +33,28 @@ repos: - id: end-of-file-fixer - repo: https://github.com/domdfcoding/pre-commit-hooks - rev: v0.2.1 + rev: v0.4.0 hooks: - id: requirements-txt-sorter args: - --allow-git - id: check-docstring-first - exclude: ^(doc-source/conf|__pkginfo__|make_conda_recipe|setup|tests/.*)\.py$ + exclude: ^(doc-source/conf|__pkginfo__|setup|tests/.*)\.py$ - id: bind-requirements - - repo: https://github.com/domdfcoding/flake8-dunder-all - rev: v0.1.7 + - repo: https://github.com/python-formate/flake8-dunder-all + rev: v0.4.1 hooks: - id: ensure-dunder-all files: ^sphinx_pyproject/.*\.py$ - repo: https://github.com/domdfcoding/flake2lint - rev: v0.4.0 + rev: v0.4.3 hooks: - id: flake2lint - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.8.0 + rev: v1.10.0 hooks: - id: python-no-eval - id: rst-backticks @@ -54,7 +62,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/asottile/pyupgrade - rev: v2.11.0 + rev: v2.12.0 hooks: - id: pyupgrade args: @@ -62,19 +70,24 @@ repos: - --keep-runtime-typing - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.9 + rev: v1.5.1 hooks: - id: remove-crlf - id: forbid-crlf - - repo: https://github.com/repo-helper/formate - rev: v0.4.3 + - repo: https://github.com/python-formate/snippet-fmt + rev: v0.1.5 + hooks: + - id: snippet-fmt + + - repo: https://github.com/python-formate/formate + rev: v0.8.0 hooks: - id: formate - exclude: ^(doc-source/conf|__pkginfo__|make_conda_recipe|setup)\.(_)?py$ + exclude: ^(doc-source/conf|__pkginfo__|setup)\.(_)?py$ - - repo: https://github.com/domdfcoding/dep_checker - rev: v0.6.0 + - repo: https://github.com/python-coincidence/dep_checker + rev: v0.8.0 hooks: - id: dep_checker args: diff --git a/.pylintrc b/.pylintrc index a21206a..81ecba0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -66,7 +66,7 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=all -enable=assert-on-tuple,astroid-error,bad-except-order,bad-inline-option,bad-option-value,bad-reversed-sequence,bare-except,binary-op-exception,boolean-datetime,catching-non-exception,cell-var-from-loop,confusing-with-statement,consider-merging-isinstance,consider-using-enumerate,consider-using-ternary,continue-in-finally,cyclic-import,deprecated-pragma,django-not-available,duplicate-except,duplicate-key,eval-used,exec-used,expression-not-assigned,fatal,file-ignored,fixme,global-at-module-level,global-statement,global-variable-not-assigned,global-variable-undefined,http-response-with-content-type-json,http-response-with-json-dumps,invalid-all-object,invalid-characters-in-docstring,len-as-condition,literal-comparison,locally-disabled,locally-enabled,lost-exception,lowercase-l-suffix,misplaced-bare-raise,missing-kwoa,mixed-line-endings,model-has-unicode,model-missing-unicode,model-no-explicit-unicode,model-unicode-not-callable,multiple-imports,new-db-field-with-default,non-ascii-bytes-literals,nonexistent-operator,not-in-loop,notimplemented-raised,overlapping-except,parse-error,pointless-statement,pointless-string-statement,raising-bad-type,raising-non-exception,raw-checker-failed,redefine-in-handler,redefined-argument-from-local,redefined-builtin,redundant-content-type-for-json-response,reimported,relative-import,return-outside-function,simplifiable-if-statement,singleton-comparison,syntax-error,trailing-comma-tuple,trailing-newlines,unbalanced-tuple-unpacking,undefined-all-variable,undefined-loop-variable,unexpected-line-ending-format,unidiomatic-typecheck,unnecessary-lambda,unnecessary-pass,unnecessary-semicolon,unneeded-not,unpacking-non-sequence,unreachable,unrecognized-inline-option,used-before-assignment,useless-else-on-loop,using-constant-test,wildcard-import,yield-outside-function,useless-return +enable=assert-on-tuple,astroid-error,bad-except-order,bad-inline-option,bad-option-value,bad-reversed-sequence,bare-except,binary-op-exception,boolean-datetime,catching-non-exception,cell-var-from-loop,confusing-with-statement,consider-merging-isinstance,consider-using-enumerate,consider-using-ternary,continue-in-finally,deprecated-pragma,django-not-available,duplicate-except,duplicate-key,eval-used,exec-used,expression-not-assigned,fatal,file-ignored,fixme,global-at-module-level,global-statement,global-variable-not-assigned,global-variable-undefined,http-response-with-content-type-json,http-response-with-json-dumps,invalid-all-object,invalid-characters-in-docstring,len-as-condition,literal-comparison,locally-disabled,locally-enabled,lost-exception,lowercase-l-suffix,misplaced-bare-raise,missing-kwoa,mixed-line-endings,model-has-unicode,model-missing-unicode,model-no-explicit-unicode,model-unicode-not-callable,multiple-imports,new-db-field-with-default,non-ascii-bytes-literals,nonexistent-operator,not-in-loop,notimplemented-raised,overlapping-except,parse-error,pointless-statement,pointless-string-statement,raising-bad-type,raising-non-exception,raw-checker-failed,redefine-in-handler,redefined-argument-from-local,redefined-builtin,redundant-content-type-for-json-response,reimported,relative-import,return-outside-function,simplifiable-if-statement,singleton-comparison,syntax-error,trailing-comma-tuple,trailing-newlines,unbalanced-tuple-unpacking,undefined-all-variable,undefined-loop-variable,unexpected-line-ending-format,unidiomatic-typecheck,unnecessary-lambda,unnecessary-pass,unnecessary-semicolon,unneeded-not,unpacking-non-sequence,unreachable,unrecognized-inline-option,used-before-assignment,useless-else-on-loop,using-constant-test,wildcard-import,yield-outside-function,useless-return [REPORTS] diff --git a/.readthedocs.yml b/.readthedocs.yml index 41d2e57..83fc025 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,9 +9,16 @@ formats: - pdf - htmlzip python: - version: 3.8 install: - requirements: requirements.txt - requirements: doc-source/requirements.txt - - method: pip - path: . +build: + os: ubuntu-22.04 + tools: + python: '3.9' + jobs: + post_create_environment: + - pip install . + post_install: + - pip install sphinxcontrib-applehelp==1.0.4 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.1 + sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 diff --git a/README.rst b/README.rst index e63fcba..ce1faee 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ sphinx-pyproject * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - - |codefactor| |actions_flake8| |actions_mypy| |pre_commit_ci| + - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| @@ -58,8 +58,8 @@ sphinx-pyproject :target: https://github.com/sphinx-toolbox/sphinx-pyproject/actions?query=workflow%3A%22mypy%22 :alt: mypy status -.. |requires| image:: https://requires.io/github/sphinx-toolbox/sphinx-pyproject/requirements.svg?branch=master - :target: https://requires.io/github/sphinx-toolbox/sphinx-pyproject/requirements/?branch=master +.. |requires| image:: https://dependency-dash.repo-helper.uk/github/sphinx-toolbox/sphinx-pyproject/badge.svg + :target: https://dependency-dash.repo-helper.uk/github/sphinx-toolbox/sphinx-pyproject/ :alt: Requirements Status .. |coveralls| image:: https://img.shields.io/coveralls/github/sphinx-toolbox/sphinx-pyproject/master?logo=coveralls @@ -101,7 +101,7 @@ sphinx-pyproject .. |language| image:: https://img.shields.io/github/languages/top/sphinx-toolbox/sphinx-pyproject :alt: GitHub top language -.. |commits-since| image:: https://img.shields.io/github/commits-since/sphinx-toolbox/sphinx-pyproject/v0.1.0 +.. |commits-since| image:: https://img.shields.io/github/commits-since/sphinx-toolbox/sphinx-pyproject/v0.3.0 :target: https://github.com/sphinx-toolbox/sphinx-pyproject/pulse :alt: GitHub commits since tagged version @@ -109,17 +109,13 @@ sphinx-pyproject :target: https://github.com/sphinx-toolbox/sphinx-pyproject/commit/master :alt: GitHub last commit -.. |maintained| image:: https://img.shields.io/maintenance/yes/2021 +.. |maintained| image:: https://img.shields.io/maintenance/yes/2025 :alt: Maintenance .. |pypi-downloads| image:: https://img.shields.io/pypi/dm/sphinx-pyproject :target: https://pypi.org/project/sphinx-pyproject/ :alt: PyPI - Downloads -.. |pre_commit_ci| image:: https://results.pre-commit.ci/badge/github/sphinx-toolbox/sphinx-pyproject/master.svg - :target: https://results.pre-commit.ci/latest/github/sphinx-toolbox/sphinx-pyproject/master - :alt: pre-commit.ci status - .. end shields Installation @@ -141,8 +137,8 @@ To install with ``conda``: .. code-block:: bash - $ conda config --add channels http://conda.anaconda.org/conda-forge - $ conda config --add channels http://conda.anaconda.org/domdfcoding + $ conda config --add channels https://conda.anaconda.org/conda-forge + $ conda config --add channels https://conda.anaconda.org/domdfcoding * Then install @@ -151,3 +147,71 @@ To install with ``conda``: $ conda install sphinx-pyproject .. end installation + +Usage +------- + +The ``SphinxConfig`` class will load the configuration from ``pyproject.toml``. +By passing ``globalns=globals()`` to the class constructor, the keys parsed from the +``pyproject.toml`` file will be added to the global namespace of the ``conf.py`` file. + +For example: + +.. code-block:: python3 + + # conf.py + + from sphinx_pyproject import SphinxConfig + + config = SphinxConfig("../pyproject.toml", globalns=globals()) + + author # This name *looks* to be undefined, but it isn't. + +The ``SphinxConfig`` class also provides a ``collections.abc.Mapping`` interface. +If you are going to override or modify one of the configuration values after parsing it, +the recommended approach is to explicitly assign the name: + +.. code-block:: python + + extensions = config["extensions"] + extensions.append("sphinx.ext.autodoc") + +This will prevent warnings from linters etc., but is not necessary for Sphinx to see the configuration. + +Additionally the ``SphinxConfig`` class takes an optional parameter ``config_overrides`` that +can be used to dynamically update values from ``pyproject.toml``. This can be helpful for setting +dynamic values like ``version``. + +.. code-block:: python3 + + # conf.py + from sphinx_pyproject import SphinxConfig + + from myproject import __version__ as myproject_version + + config = SphinxConfig("../pyproject.toml", globalns=globals(), config_overrides = {"version": myproject_version}) + + +Configuration +^^^^^^^^^^^^^^^ + +``sphinx-pyproject`` parses the configuration from the ``[project]`` and ``[tool.sphinx-pyproject]`` tables in ``pyproject.toml``. +The ``[project]`` table is defined in `PEP 621`_. +``sphinx-pyproject`` only uses the following keys: + +* name_ – The name of the project. +* version_ – The version of the project. +* description_ – The summary description of the project. +* One of `authors/maintainers`_. + +The remaining `Sphinx configuration values`_ can be provided in the ``[tool.sphinx-pyproject]`` table. + +See `this project's pyproject.toml file`_ for an example of this configuration. + +.. _PEP 621: https://www.python.org/dev/peps/pep-0621/#authors-maintainers +.. _name: https://www.python.org/dev/peps/pep-0621/#name +.. _version: https://www.python.org/dev/peps/pep-0621/#version +.. _description: https://www.python.org/dev/peps/pep-0621/#description +.. _authors/maintainers: https://www.python.org/dev/peps/pep-0621/#authors-maintainers +.. _Sphinx configuration values: https://www.sphinx-doc.org/en/master/usage/configuration.html +.. _this project's pyproject.toml file: https://github.com/sphinx-toolbox/sphinx-pyproject/blob/master/pyproject.toml diff --git a/__pkginfo__.py b/__pkginfo__.py deleted file mode 100644 index cc6c1fe..0000000 --- a/__pkginfo__.py +++ /dev/null @@ -1,32 +0,0 @@ -# This file is managed by 'repo_helper'. Don't edit it directly. -# Copyright © 2020 Dominic Davis-Foster -# -# This file is distributed under the same license terms as the program it came with. -# There will probably be a file called LICEN[S/C]E in the same directory as this file. -# -# In any case, this program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# This script based on https://github.com/rocky/python-uncompyle6/blob/master/__pkginfo__.py -# - -# stdlib -import pathlib - -__all__ = [ - "__copyright__", - "__version__", - "repo_root", - "install_requires", - "extras_require", - ] - -__copyright__ = """ -2021 Dominic Davis-Foster -""" - -__version__ = "0.1.0" -repo_root = pathlib.Path(__file__).parent -install_requires = (repo_root / "requirements.txt").read_text(encoding="utf-8").split('\n') -extras_require = {} diff --git a/doc-source/Source.rst b/doc-source/Source.rst index d0af70f..44ac328 100644 --- a/doc-source/Source.rst +++ b/doc-source/Source.rst @@ -7,24 +7,27 @@ and can be accessed from the following URL: https://github.com/sphinx-toolbox/sp If you have ``git`` installed, you can clone the repository with the following command: -.. code-block:: bash +.. prompt:: bash + + git clone https://github.com/sphinx-toolbox/sphinx-pyproject + +.. parsed-literal:: - $ git clone https://github.com/sphinx-toolbox/sphinx-pyproject" - > Cloning into 'sphinx-pyproject'... - > remote: Enumerating objects: 47, done. - > remote: Counting objects: 100% (47/47), done. - > remote: Compressing objects: 100% (41/41), done. - > remote: Total 173 (delta 16), reused 17 (delta 6), pack-reused 126 - > Receiving objects: 100% (173/173), 126.56 KiB | 678.00 KiB/s, done. - > Resolving deltas: 100% (66/66), done. + Cloning into 'sphinx-pyproject'... + remote: Enumerating objects: 47, done. + remote: Counting objects: 100% (47/47), done. + remote: Compressing objects: 100% (41/41), done. + remote: Total 173 (delta 16), reused 17 (delta 6), pack-reused 126 + Receiving objects: 100% (173/173), 126.56 KiB | 678.00 KiB/s, done. + Resolving deltas: 100% (66/66), done. | Alternatively, the code can be downloaded in a 'zip' file by clicking: | :guilabel:`Clone or download` --> :guilabel:`Download Zip` .. figure:: git_download.png - :alt: Downloading a 'zip' file of the source code. + :alt: Downloading a 'zip' file of the source code. - Downloading a 'zip' file of the source code + Downloading a 'zip' file of the source code Building from source diff --git a/doc-source/_static/style.css b/doc-source/_static/style.css index e69de29..770bfaf 100644 --- a/doc-source/_static/style.css +++ b/doc-source/_static/style.css @@ -0,0 +1,7 @@ +.compound-first, .compound-middle { + margin-bottom: 10px; +} + +.compound-first div.highlight, .compound-middle div.highlight { + margin-bottom: 10px !important; +} diff --git a/doc-source/_templates/layout.html b/doc-source/_templates/layout.html index a52c77f..b363b10 100644 --- a/doc-source/_templates/layout.html +++ b/doc-source/_templates/layout.html @@ -2,4 +2,5 @@ {% extends "!layout.html" %} {% block extrahead %} + {% endblock %} diff --git a/doc-source/api.rst b/doc-source/api.rst index f5c2416..560c28c 100644 --- a/doc-source/api.rst +++ b/doc-source/api.rst @@ -2,5 +2,10 @@ API Reference ================= +.. latex:vspace:: -20px + +.. autosummary-widths:: 45/100 + .. automodule:: sphinx_pyproject :member-order: bysource + :exclude-members: keys diff --git a/doc-source/conf.py b/doc-source/conf.py index e19a247..41d951f 100644 --- a/doc-source/conf.py +++ b/doc-source/conf.py @@ -1,14 +1,21 @@ #!/usr/bin/env python3 +# This file is managed by 'repo_helper'. Don't edit it directly. + # stdlib import os import re +import sys -# this package +# 3rd party from sphinx_pyproject import SphinxConfig +sys.path.append('.') config = SphinxConfig(globalns=globals()) +project = config["project"] +author = config["author"] +documentation_summary = config.description github_url = "https://github.com/{github_username}/{github_repository}".format_map(config) @@ -17,14 +24,11 @@ .. |browse_github| replace:: `Browse the GitHub Repository <{github_url}>`__ """ -project = config.name slug = re.sub(r'\W+', '-', project.lower()) -htmlhelp_basename = slug - -release = config.version -documentation_summary = config.description +release = version = config.version -todo_include_todos = bool(os.environ.get("SHOW_TODOS", 0)) +sphinx_builder = os.environ.get("SPHINX_BUILDER", "html").lower() +todo_include_todos = int(os.environ.get("SHOW_TODOS", 0)) and sphinx_builder != "latex" intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), @@ -32,27 +36,22 @@ "sphinx-toolbox": ("https://sphinx-toolbox.readthedocs.io/en/latest", None), } -latex_documents = [("index", f'{slug}.tex', project, config.author, "manual")] -man_pages = [("index", slug, project, [config.author], 1)] -texinfo_documents = [("index", slug, project, config.author, slug, project, "Miscellaneous")] - -toctree_plus_types = { - "class", - "function", - "method", - "data", - "enum", - "flag", - "confval", - "directive", - "role", - "confval", - "protocol", - "typeddict", - "namedtuple", - "exception", +html_theme_options = {"logo_only": False} + +html_context = { + "display_github": True, + "github_user": "sphinx-toolbox", + "github_repo": "sphinx-pyproject", + "github_version": "master", + "conf_py_path": "/doc-source/", } +htmlhelp_basename = slug + +latex_documents = [("index", f'{slug}.tex', project, author, "manual")] +man_pages = [("index", slug, project, [author], 1)] +texinfo_documents = [("index", slug, project, author, slug, project, "Miscellaneous")] +toctree_plus_types = set(config["toctree_plus_types"]) autodoc_default_options = { "members": None, # Include all members (methods). @@ -61,3 +60,39 @@ "show-inheritance": None, "exclude-members": ','.join(config["autodoc_exclude_members"]), } + +latex_elements = { + "printindex": "\\begin{flushleft}\n\\printindex\n\\end{flushleft}", + "tableofcontents": "\\pdfbookmark[0]{\\contentsname}{toc}\\sphinxtableofcontents", + } + + +# Fix for pathlib issue with sphinxemoji on Python 3.9 and Sphinx 4.x +def copy_asset_files(app, exc): + # 3rd party + from domdf_python_tools.compat import importlib_resources + from sphinx.util.fileutil import copy_asset + + if exc: + return + + asset_files = ["twemoji.js", "twemoji.css"] + for path in asset_files: + path_str = os.fspath(importlib_resources.files("sphinxemoji") / path) + copy_asset(path_str, os.path.join(app.outdir, "_static")) + + +def setup(app): + # 3rd party + from sphinx_toolbox.latex import better_header_layout + from sphinxemoji import sphinxemoji + + app.connect("config-inited", lambda app, config: better_header_layout(config)) + app.connect("build-finished", copy_asset_files) + app.add_js_file("https://unpkg.com/twemoji@latest/dist/twemoji.min.js") + app.add_js_file("twemoji.js") + app.add_css_file("twemoji.css") + app.add_transform(sphinxemoji.EmojiSubstitutions) + + +needspace_amount = r'5\baselineskip' diff --git a/doc-source/contributing.rst b/doc-source/contributing.rst deleted file mode 100644 index 69b7865..0000000 --- a/doc-source/contributing.rst +++ /dev/null @@ -1,72 +0,0 @@ -Overview ---------- - -.. This file based on https://github.com/PyGithub/PyGithub/blob/master/CONTRIBUTING.md - -``sphinx-pyproject`` uses `tox `_ to automate testing and packaging, -and `pre-commit `_ to maintain code quality. - -Install ``pre-commit`` with ``pip`` and install the git hook: - -.. prompt:: bash - - python -m pip install pre-commit - pre-commit install - - -Coding style --------------- - -`formate `_ is used for code formatting. - -It can be run manually via ``pre-commit``: - -.. prompt:: bash - - pre-commit run formate -a - - -Or, to run the complete autoformatting suite: - -.. prompt:: bash - - pre-commit run -a - - -Automated tests -------------------- - -Tests are run with ``tox`` and ``pytest``. -To run tests for a specific Python version, such as Python 3.6: - -.. prompt:: bash - - tox -e py36 - - -To run tests for all Python versions, simply run: - -.. prompt:: bash - - tox - - -Type Annotations -------------------- - -Type annotations are checked using ``mypy``. Run ``mypy`` using ``tox``: - -.. prompt:: bash - - tox -e mypy - - - -Build documentation locally ------------------------------- - -The documentation is powered by Sphinx. A local copy of the documentation can be built with ``tox``: - -.. prompt:: bash - - tox -e docs diff --git a/doc-source/index.rst b/doc-source/index.rst index 282f6f1..9aec0ce 100644 --- a/doc-source/index.rst +++ b/doc-source/index.rst @@ -5,6 +5,7 @@ sphinx-pyproject .. start short_desc .. documentation-summary:: + :meta: .. end short_desc @@ -27,7 +28,7 @@ sphinx-pyproject * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - - |codefactor| |actions_flake8| |actions_mypy| |pre_commit_ci| + - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| @@ -59,7 +60,8 @@ sphinx-pyproject :workflow: mypy :alt: mypy status - .. |requires| requires-io-shield:: + .. |requires| image:: https://dependency-dash.repo-helper.uk/github/sphinx-toolbox/sphinx-pyproject/badge.svg + :target: https://dependency-dash.repo-helper.uk/github/sphinx-toolbox/sphinx-pyproject/ :alt: Requirements Status .. |coveralls| coveralls-shield:: @@ -105,14 +107,14 @@ sphinx-pyproject :alt: GitHub top language .. |commits-since| github-shield:: - :commits-since: v0.1.0 + :commits-since: v0.3.0 :alt: GitHub commits since tagged version .. |commits-latest| github-shield:: :last-commit: :alt: GitHub last commit - .. |maintained| maintained-shield:: 2021 + .. |maintained| maintained-shield:: 2025 :alt: Maintenance .. |pypi-downloads| pypi-shield:: @@ -120,9 +122,6 @@ sphinx-pyproject :downloads: month :alt: PyPI - Downloads - .. |pre_commit_ci| pre-commit-ci-shield:: - :alt: pre-commit.ci status - .. end shields Installation @@ -138,6 +137,12 @@ Installation .. end installation +Contents +----------- + +.. html-section:: + + .. toctree:: :hidden: @@ -145,18 +150,20 @@ Installation .. toctree:: :maxdepth: 3 - :caption: Documentation :glob: usage api + Source .. toctree:: - :maxdepth: 3 - :caption: Contributing + :caption: Links + :hidden: - contributing - Source + GitHub + PyPI + Contributing Guide + license .. start links @@ -164,6 +171,6 @@ Installation View the :ref:`Function Index ` or browse the `Source Code <_modules/index.html>`__. - `Browse the GitHub Repository `__ + :github:repo:`Browse the GitHub Repository ` .. end links diff --git a/doc-source/license.rst b/doc-source/license.rst new file mode 100644 index 0000000..605055b --- /dev/null +++ b/doc-source/license.rst @@ -0,0 +1,10 @@ +========= +License +========= + +``sphinx-pyproject`` is licensed under the :choosealicense:`MIT` + +.. license-info:: MIT + +.. license:: + :py: sphinx-pyproject diff --git a/doc-source/requirements.txt b/doc-source/requirements.txt index ee39b88..b4a8493 100644 --- a/doc-source/requirements.txt +++ b/doc-source/requirements.txt @@ -1,15 +1,23 @@ -autodocsumm>=0.2.0 -default-values>=0.4.2 +git+https://github.com/sphinx-toolbox/sphinx-toolbox-experimental +default-values>=0.6.0 domdf-sphinx-theme>=0.3.0 -extras-require>=0.2.0 -seed-intersphinx-mapping>=0.3.1 -sphinx<3.4.0,>=3.0.3 -sphinx-copybutton>=0.2.12 -sphinx-debuginfo>=0.1.0 -sphinx-notfound-page>=0.5 -sphinx-prompt>=1.1.0 -sphinx-tabs>=1.1.13 -sphinx-toolbox>=2.2.0 +extras-require>=0.5.0 +html-section>=0.3.0 +seed-intersphinx-mapping>=1.2.2 +sphinx>=3.0.3 +sphinx-copybutton>=0.5.2 +sphinx-debuginfo>=0.2.2 +sphinx-licenseinfo>=0.3.1 +sphinx-notfound-page>=0.7.1 +sphinx-packaging>=0.2.0 +sphinx-pyproject>=0.1.0 +sphinx-toolbox>=3.5.0 +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-httpdomain>=1.7.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 sphinxemoji>=0.1.6 -toctree-plus>=0.1.0 +toctree-plus>=0.6.1 diff --git a/doc-source/usage.rst b/doc-source/usage.rst index e36e932..3df6b4c 100644 --- a/doc-source/usage.rst +++ b/doc-source/usage.rst @@ -1,31 +1,79 @@ -================= +======= Usage -================= - +======= The :class:`~.SphinxConfig` class will load the configuration from ``pyproject.toml``. By passing :func:`globalns=globals() ` to the class constructor, the keys parsed from the ``pyproject.toml`` file will be added to the global namespace of the ``conf.py`` file. -For example: +.. compound:: -.. code-block:: python + For example: - # conf.py + .. code-block:: python3 + + # conf.py + + from sphinx_pyproject import SphinxConfig + + config = SphinxConfig("../pyproject.toml", globalns=globals()) + + author # This name *looks* to be undefined, but it isn't. + + +.. compound:: + + The :class:`~.SphinxConfig` class also provides a :class:`collections.abc.Mapping` interface. + If you are going to override or modify one of the configuration values after parsing it, + the recommended approach is to explicitly assign the name: + + .. code-block:: python + + extensions = config["extensions"] + extensions.append("sphinx.ext.autodoc") + + This will prevent warnings from linters etc., but is not necessary for Sphinx to see the configuration. + +.. note:: + + At time of writing the "Poetry" tool does not support PEP 621. To enable a mode compatible with + the ``[tool.poetry]`` table supply the argument ``style="poetry"``. For example: + + .. code-block:: python + + config = SphinxConfig("../pyproject.toml", style="poetry") + + +Additionally the ``SphinxConfig`` class takes an optional parameter ``config_overrides`` that +can be used to dynamically update values from ``pyproject.toml``. This can be helpful for setting +dynamic values like ``version``. + +.. code-block:: python3 + + # conf.py from sphinx_pyproject import SphinxConfig - config = SphinxConfig("../pyproject.toml", globalns=globals()) + from myproject import __version__ as myproject_version + + config = SphinxConfig("../pyproject.toml", globalns=globals(), config_overrides = {"version": myproject_version}) + + +Configuration +---------------- - author # This name *looks* to be undefined, but it isn't. +``sphinx-pyproject`` parses the configuration from the ``[project]`` and ``[tool.sphinx-pyproject]`` tables in ``pyproject.toml``. +The ``[project]`` table is defined in :pep:`621`. +``sphinx-pyproject`` only uses the following keys: -The :class:`~.SphinxConfig` class also provides a :class:`collections.abc.Mapping` interface. -If you are going to override or modify one of the configuration values after parsing it, -the recommended approach is to explicitly assign the name: +* :pep621:`name` – The name of the project. +* :pep621:`version` – The version of the project. +* :pep621:`description` – The summary description of the project. +* One of :pep621:`authors/maintainers`. -.. code-block:: python +The remaining `Sphinx configuration values`_ can be provided in the ``[tool.sphinx-pyproject]`` table. - extensions = config["extensions"] - extensions.append("sphinx.ext.autodoc") +See `this project's pyproject.toml file`_ for an example of this configuration. -This will prevent warnings from linters etc., but is not necessary for Sphinx to see the configuration. +.. _Sphinx configuration values: https://www.sphinx-doc.org/en/master/usage/configuration.html +.. _this project's pyproject.toml file: https://github.com/sphinx-toolbox/sphinx-pyproject/blob/master/pyproject.toml diff --git a/formate.toml b/formate.toml index bf9f678..053fafa 100644 --- a/formate.toml +++ b/formate.toml @@ -6,21 +6,17 @@ noqa-reformat = 60 ellipsis-reformat = 70 squish_stubs = 80 -[config] -indent = "\t" -line_length = 115 - [hooks.yapf] priority = 30 -[hooks.isort] -priority = 50 - [hooks.yapf.kwargs] yapf_style = ".style.yapf" +[hooks.isort] +priority = 50 + [hooks.isort.kwargs] -indent = "\t\t" +indent = " " multi_line_output = 8 import_heading_stdlib = "stdlib" import_heading_thirdparty = "3rd party" @@ -39,6 +35,7 @@ known_third_party = [ "dom_toml", "domdf_python_tools", "email_validator", + "importlib_metadata", "packaging", "pytest", "pytest_cov", @@ -47,4 +44,8 @@ known_third_party = [ "shippinglabel", "toml", ] -known_first_party = "sphinx_pyproject" +known_first_party = [ "sphinx_pyproject",] + +[config] +indent = " " +line_length = 115 diff --git a/justfile b/justfile new file mode 100644 index 0000000..e8ed871 --- /dev/null +++ b/justfile @@ -0,0 +1,22 @@ +default: lint + +pdf-docs: latex-docs + make -C doc-source/build/latex/ + +latex-docs: + SPHINX_BUILDER=latex tox -e docs + +unused-imports: + tox -e lint -- --select F401 + +incomplete-defs: + tox -e lint -- --select MAN + +vdiff: + git diff $(repo-helper show version -q)..HEAD + +bare-ignore: + greppy '# type:? *ignore(?!\[|\w)' -s + +lint: unused-imports incomplete-defs bare-ignore + tox -n qa diff --git a/pyproject.toml b/pyproject.toml index be9e5ea..7f4ba61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,20 +4,19 @@ build-backend = "whey" [project] name = "sphinx-pyproject" -version = "0.1.0" +version = "0.3.0" description = "Move some of your Sphinx configuration into pyproject.toml" readme = "README.rst" -keywords = [ "sphinx", "documentation", "pep621", "toml",] +keywords = [ "documentation", "pep621", "sphinx", "toml",] dynamic = [ "requires-python", "classifiers", "dependencies",] -[[project.authors]] -email = "dominic@davis-foster.co.uk" -name = "Dominic Davis-Foster" - - [project.license] file = "LICENSE" +[[project.authors]] +name = "Dominic Davis-Foster" +email = "dominic@davis-foster.co.uk" + [project.urls] Homepage = "https://github.com/sphinx-toolbox/sphinx-pyproject" "Issue Tracker" = "https://github.com/sphinx-toolbox/sphinx-pyproject/issues" @@ -35,7 +34,7 @@ base-classifiers = [ "Topic :: Utilities", "Typing :: Typed", ] -python-versions = [ "3.6", "3.7", "3.8", "3.9",] +python-versions = [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13",] python-implementations = [ "CPython", "PyPy",] platforms = [ "Windows", "macOS", "Linux",] license-key = "MIT" @@ -44,7 +43,9 @@ package = "sphinx_pyproject" [tool.sphinx-pyproject] github_username = "sphinx-toolbox" github_repository = "sphinx-pyproject" -copyright = "2021 Dominic Davis-Foster" +author = "Dominic Davis-Foster" +project = "sphinx-pyproject" +copyright = "2021-2023 Dominic Davis-Foster" language = "en" package_root = "sphinx_pyproject" extensions = [ @@ -53,21 +54,27 @@ extensions = [ "sphinx_toolbox.more_autosummary", "sphinx_toolbox.documentation_summary", "sphinx_toolbox.tweaks.param_dash", + "sphinxcontrib.toctree_plus", + "sphinx_toolbox.tweaks.latex_layout", "sphinx_toolbox.tweaks.latex_toc", "sphinx.ext.intersphinx", "sphinx.ext.mathjax", - "sphinxcontrib.httpdomain", "sphinxcontrib.extras_require", "sphinx.ext.todo", - "sphinxemoji.sphinxemoji", "notfound.extension", "sphinx_copybutton", "sphinxcontrib.default_values", - "sphinxcontrib.toctree_plus", "sphinx_debuginfo", + "sphinx_licenseinfo", "seed_intersphinx_mapping", + "html_section", + "sphinx_toolbox.more_autosummary.column_widths", + "sphinx_toolbox.latex", + "sphinx_toolbox.latex.succinct_seealso", + "sphinx_toolbox_experimental.missing_xref", + "sphinx_toolbox_experimental.changelog", + "sphinx_packaging.peps", ] -sphinxemoji_style = "twemoji" gitstamp_fmt = "%d %b %Y" templates_path = [ "_templates",] html_static_path = [ "_static",] @@ -78,10 +85,25 @@ pygments_style = "default" html_theme = "domdf_sphinx_theme" html_theme_path = [ "../..",] html_show_sourcelink = true +toctree_plus_types = [ + "class", + "confval", + "data", + "directive", + "enum", + "exception", + "flag", + "function", + "namedtuple", + "protocol", + "role", + "typeddict", +] add_module_names = false hide_none_rtype = true all_typevars = true overloads_location = "bottom" +html_codeblock_linenos_style = "table" autodoc_exclude_members = [ "__dict__", "__class__", @@ -100,33 +122,41 @@ autodoc_exclude_members = [ "__abstractmethods__", "__hash__", ] -toctree_plus_types = [ - "class", - "function", - "method", - "data", - "enum", - "flag", - "confval", - "directive", - "role", - "confval", - "protocol", - "typeddict", - "namedtuple", - "exception", -] [tool.mkrecipe] conda-channels = [ "conda-forge", "domdfcoding",] extras = "all" -[tool.sphinx-pyproject.html_theme_options] -logo_only = false +[tool.mypy] +python_version = "3.9" +namespace_packages = true +check_untyped_defs = true +warn_unused_ignores = true +no_implicit_optional = true +show_error_codes = true + +[tool.snippet-fmt] +directives = [ "code-block",] + +[tool.snippet-fmt.languages.python] +reformat = true + +[tool.snippet-fmt.languages.python3] + +[tool.snippet-fmt.languages.TOML] +reformat = true + +[tool.snippet-fmt.languages.ini] + +[tool.snippet-fmt.languages.json] + +[tool.dependency-dash."requirements.txt"] +order = 10 + +[tool.dependency-dash."tests/requirements.txt"] +order = 20 +include = false -[tool.sphinx-pyproject.html_context] -display_github = true -github_user = "sphinx-toolbox" -github_repo = "sphinx-pyproject" -github_version = "master" -conf_py_path = "/doc-source/" +[tool.dependency-dash."doc-source/requirements.txt"] +order = 30 +include = false diff --git a/repo_helper.yml b/repo_helper.yml index b4c0aa6..66e5cef 100644 --- a/repo_helper.yml +++ b/repo_helper.yml @@ -1,30 +1,36 @@ # Configuration for 'repo_helper' (https://github.com/domdfcoding/repo_helper --- modname: 'sphinx-pyproject' -copyright_years: 2021 +copyright_years: 2021-2023 author: 'Dominic Davis-Foster' email: 'dominic@davis-foster.co.uk' username: 'sphinx-toolbox' assignee: 'domdfcoding' primary_conda_channel: 'domdfcoding' -version: '0.1.0' +version: '0.3.0' license: 'MIT' short_desc: 'Move some of your Sphinx configuration into pyproject.toml' use_whey: true -mypy_version: "0.812" +min_coverage: 100 +docs_fail_on_warning: true +mypy_version: 1.16 +python_deploy_version: 3.9 conda_channels: - conda-forge python_versions: - - 3.6 - 3.7 - 3.8 - 3.9 - - 3.10-dev - - pypy36 + - "3.10" + - "3.11" + - "3.12" + - "3.13" - pypy37 + - pypy38 + - pypy39 keywords: - sphinx @@ -45,4 +51,17 @@ intersphinx_mapping: - "'sphinx-toolbox': ('https://sphinx-toolbox.readthedocs.io/en/latest', None)" exclude_files: - - conf + - contributing + +preserve_custom_theme: true + +extra_sphinx_extensions: + - sphinx_toolbox.more_autosummary.column_widths + - sphinx_toolbox.latex + - sphinx_toolbox.latex.succinct_seealso + - sphinx_toolbox_experimental.missing_xref + - sphinx_toolbox_experimental.changelog + - sphinx_packaging.peps + +sphinx_conf_epilogue: + - "needspace_amount = r'5\\baselineskip'" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index cd5c33a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,13 +0,0 @@ -# This file is managed by 'repo_helper'. -# You may add new sections, but any changes made to the following sections will be lost: -# * metadata -# * options -# * options.packages.find -# * mypy -# * options.entry_points - -[mypy] -python_version = 3.6 -namespace_packages = True -check_untyped_defs = True -warn_unused_ignores = True diff --git a/sphinx_pyproject/__init__.py b/sphinx_pyproject/__init__.py index a6a4315..c544f9e 100644 --- a/sphinx_pyproject/__init__.py +++ b/sphinx_pyproject/__init__.py @@ -27,6 +27,7 @@ # # stdlib +import re from typing import Any, Dict, Iterator, List, Mapping, MutableMapping, Optional # 3rd party @@ -40,10 +41,10 @@ __author__: str = "Dominic Davis-Foster" __copyright__: str = "2021 Dominic Davis-Foster" __license__: str = "MIT License" -__version__: str = "0.1.0" +__version__: str = "0.3.0" __email__: str = "dominic@davis-foster.co.uk" -__all__ = ["SphinxConfig", "ProjectParser"] +__all__ = ["SphinxConfig", "ProjectParser", "PoetryProjectParser"] class SphinxConfig(Mapping[str, Any]): @@ -51,15 +52,27 @@ class SphinxConfig(Mapping[str, Any]): Read the Sphinx configuration from ``pyproject.toml``. :param pyproject_file: The path to the ``pyproject.toml`` file. - :param globalns: The global namespace of, for example, the ``conf.py`` file. + :param globalns: The global namespace of the ``conf.py`` file. The variables parsed from the ``[tool.sphinx-pyproject]`` table will be added to this namespace. - If :py:obj:`None` this does not happen. + By default, or if explicitly :py:obj:`None`, this does not happen. + :no-default globalns: + :param style: Either ``pep621`` (default), or ``poetry`` to read configuration from the ``[tool.poetry]`` table. + :no-default style: + :param config_overrides: Custom configuration overrides. + This parameter can be used to dynamically update values from ``pyproject.toml``. + This can be used to patch dynamic values like ``version``. + By default, or if explicitly :py:obj:`None`, no config updates are performed. + :no-default config_overrides: + + .. versionchanged:: 0.2.0 Added the ``style`` keyword argument. + .. versionchanged:: 0.3.0 Added the ``config_overrides`` keyword argument. + + .. autosummary-widths:: 1/4 """ name: str """ - The value of the `project.name `_ - key in the :pep:`621` metadata. + The value of the :pep621:`project.name ` key in the :pep:`621` metadata. Underscores are replaced by dashes but :pep:`508` normalization is *not* applied. @@ -77,24 +90,22 @@ class SphinxConfig(Mapping[str, Any]): version: str """ - The value of the `project.version `_ - key in the :pep:`621` metadata. + The value of the :pep621:`project.version ` key in the :pep:`621` metadata. Converted to a string if the value was a number in the ``pyproject.toml`` file. """ description: str """ - The value of the `project.description `_ - key in the :pep:`621` metadata. + The value of the :pep621:`project.description ` key in the :pep:`621` metadata. """ author: str """ A string giving the names of the authors. - This is parsed from the `project.authors `_ - key in the :pep:`621` metadata, or the ``project.maintainers`` key as a fallback. + This is parsed from the :pep621:`project.authors ` key in the :pep:`621` metadata, + or the :pep621:`project.maintainers ` key as a fallback. The names are joined together, e.g.: @@ -122,26 +133,34 @@ def __init__( pyproject_file: PathLike = "../pyproject.toml", *, globalns: Optional[MutableMapping] = None, + style: str = "pep621", + config_overrides: Optional[MutableMapping] = None, ): pyproject_file = PathPlus(pyproject_file).abspath() config = dom_toml.load(pyproject_file, decoder=TomlPureDecoder) - if "project" not in config: - raise BadConfigError(f"No 'project' table found in {pyproject_file.as_posix()}") + parser_cls = project_parser_styles.get(style) + if parser_cls is None: + styles = ", ".join(project_parser_styles) + raise ValueError(f"'style' argument must be one of: {styles}") + + namespace = parser_cls.get_namespace(pyproject_file, config) + pep621_config = parser_cls().parse(namespace) - pep621_config = ProjectParser().parse(config["project"]) + for key, value in (config_overrides or {}).items(): + pep621_config[key] = value for key in ("name", "version", "description"): if key not in pep621_config: raise BadConfigError( - f"Either {key!r} was not declared in the 'project' table " + f"Either {key!r} was not declared in the 'project' table " f"or it was marked as 'dynamic', which is unsupported by 'sphinx-pyproject'." ) if "author" not in pep621_config: raise BadConfigError( - f"Either 'authors/maintainers' was not declared in the 'project' table " + f"Either 'authors/maintainers' was not declared in the 'project' table " f"or it was marked as 'dynamic', which is unsupported by 'sphinx-pyproject'." ) @@ -156,7 +175,7 @@ def __init__( globalns.update(pep621_config) globalns.update(self._freeform) - def __getitem__(self, item) -> Any: + def __getitem__(self, item: str) -> Any: """ Returns the value of the given key in the ``tool.sphinx-pyproject`` table. @@ -183,11 +202,29 @@ def __iter__(self) -> Iterator[str]: class ProjectParser(AbstractConfigParser): """ Parser for :pep:`621` metadata from ``pyproject.toml``. + + .. autosummary-widths:: 7/16 """ + @staticmethod + def get_namespace(filename: PathPlus, config: Dict[str, TOML_TYPES]) -> Dict[str, TOML_TYPES]: + """ + Returns the ``[project]`` table in a ``project.toml`` file. + + :param filename: The filename the TOML data was read from. Used in error messages. + :param config: The data from the TOML file. + + .. versionadded:: 0.2.0 + """ + + if "project" not in config: + raise BadConfigError(f"No 'project' table found in {filename.as_posix()}") + + return config["project"] + def parse_name(self, config: Dict[str, TOML_TYPES]) -> str: """ - Parse the `name `_ key. + Parse the :pep621:`name` key. :param config: The unparsed TOML config for the ``[project]`` table. """ @@ -198,7 +235,7 @@ def parse_name(self, config: Dict[str, TOML_TYPES]) -> str: def parse_version(self, config: Dict[str, TOML_TYPES]) -> str: """ - Parse the `version `_ key. + Parse the :pep621:`version` key. :param config: The unparsed TOML config for the ``[project]`` table. """ @@ -209,7 +246,7 @@ def parse_version(self, config: Dict[str, TOML_TYPES]) -> str: def parse_description(self, config: Dict[str, TOML_TYPES]) -> str: """ - Parse the `description `_ key. + Parse the :pep621:`description` key. :param config: The unparsed TOML config for the ``[project]`` table. """ @@ -221,7 +258,7 @@ def parse_description(self, config: Dict[str, TOML_TYPES]) -> str: @staticmethod def parse_author(config: Dict[str, TOML_TYPES]) -> str: """ - Parse the `authors/maintainers `_ key. + Parse the :pep621:`authors/maintainers` key. :param config: The unparsed TOML config for the ``[project]`` table. """ @@ -274,3 +311,51 @@ def parse( config["author"] = config.pop("maintainers") return super().parse(config) + + +class PoetryProjectParser(ProjectParser): + """ + Parser for poetry metadata from ``pyproject.toml``. + + .. versionadded:: 0.2.0 + """ + + @staticmethod + def get_namespace(filename: PathPlus, config: Dict[str, TOML_TYPES]) -> Dict[str, TOML_TYPES]: + """ + Returns the ``[tool.poetry]`` table in a ``project.toml`` file. + + :param filename: The filename the TOML data was read from. Used in error messages. + :param config: The data from the TOML file. + """ + + result = config.get("tool", {}).get("poetry") + if result is None: + raise BadConfigError(f"No 'tool.poetry' table found in {filename.as_posix()}") + + return result + + @staticmethod + def parse_author(config: Dict[str, TOML_TYPES]) -> str: + """ + Parse poetry's authors key. + + :param config: The unparsed TOML config for the ``[tool.poetry]`` table. + """ + + pep621_style_authors: List[Dict[str, str]] = [] + + for author in config["author"]: + match = re.match(r"(?P.*)<(?P.*)>", author) + if match: + name = match.group("name").strip() + email = match.group("email").strip() + pep621_style_authors.append({"name": name, "email": email}) + + return ProjectParser.parse_author({"author": pep621_style_authors}) + + +project_parser_styles = { + "pep621": ProjectParser, + "poetry": PoetryProjectParser, + } diff --git a/tests/requirements.txt b/tests/requirements.txt index 4877ea8..13a4474 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,8 +3,9 @@ coincidence>=0.2.0 coverage>=5.1 coverage-pyver-pragma>=0.2.1 domdf-python-tools[testing]>=2.0.1 +importlib-metadata>=3.6.0 iniconfig!=1.1.0,>=1.0.1 pytest>=6.0.0 pytest-cov>=2.8.1 -pytest-randomly>=3.6.0 +pytest-randomly>=3.7.0 pytest-timeout>=1.4.2 diff --git a/tests/test_sphinx_pyproject.py b/tests/test_sphinx_pyproject.py index 8fd97aa..5c26624 100644 --- a/tests/test_sphinx_pyproject.py +++ b/tests/test_sphinx_pyproject.py @@ -1,4 +1,5 @@ # stdlib +import textwrap from typing import Any, Dict # 3rd party @@ -26,10 +27,10 @@ def test_parse_our_config(advanced_data_regression: AdvancedDataRegressionFixtur assert config["language"] == "en" assert config["package_root"] == "sphinx_pyproject" assert config["github_repository"] == "sphinx-pyproject" - assert config["sphinxemoji_style"] == "twemoji" + # assert config["sphinxemoji_style"] == "twemoji" assert config["templates_path"] == ["_templates"] assert config["add_module_names"] is False - assert config["html_theme_options"] == {"logo_only": False} + assert "html_theme_options" not in config advanced_data_regression.check(globalns) @@ -147,9 +148,75 @@ def test_missing_keys(tmp_pathplus: PathPlus, config: str): (tmp_pathplus / "pyproject.toml").write_text(config) err = ( - "Either '.*' was not declared in the 'project' table " + "Either '.*' was not declared in the 'project' table " "or it was marked as 'dynamic', which is unsupported by 'sphinx-pyproject'." ) with pytest.raises(BadConfigError, match=err): SphinxConfig(tmp_pathplus / "pyproject.toml") + + +POETRY_AUTHORS = """ +[tool.poetry] +name = 'foo' +version = '1.2.3' +description = 'desc' +authors = ["Person "] +""" + +POETRY_MAINTAINERS = """ +[tool.poetry] +name = 'foo' +version = '1.2.3' +description = 'desc' +maintainers = ["Person "] +""" + + +@pytest.mark.parametrize( + "toml", [ + pytest.param(POETRY_AUTHORS, id="authors"), + pytest.param(POETRY_MAINTAINERS, id="maintainers"), + ] + ) +def test_poetry(tmp_pathplus: PathPlus, toml: str): + (tmp_pathplus / "pyproject.toml").write_text(toml) + + config = SphinxConfig(tmp_pathplus / "pyproject.toml", style="poetry") + assert config.name == "foo" + assert config.version == "1.2.3" + assert config.author == "Person" + assert config.description == "desc" + + +def test_poetry_missing_heading(tmp_pathplus: PathPlus): + toml = textwrap.dedent(""" + [other.table] + name = 'foo' + """) + + (tmp_pathplus / "pyproject.toml").write_text(toml) + + err = "No 'tool.poetry' table found in" + with pytest.raises(BadConfigError, match=err): + SphinxConfig(tmp_pathplus / "pyproject.toml", style="poetry") + + +def test_invalid_style(tmp_pathplus: PathPlus): + (tmp_pathplus / "pyproject.toml").write_text('') + + err = "'style' argument must be one of: pep621, poetry" + with pytest.raises(ValueError, match=err): + SphinxConfig(tmp_pathplus / "pyproject.toml", style="other") + + +def test_config_overrides(tmp_pathplus: PathPlus): + (tmp_pathplus / "pyproject.toml").write_text(MINIMUM) + config_overrides: Dict[str, Any] = {"version": "3.2.1"} + + config = SphinxConfig(tmp_pathplus / "pyproject.toml", config_overrides=config_overrides) + + assert config.name == "foo" + assert config.author == "Dominic Davis-Foster" + assert config.version == "3.2.1" + assert config.description == "Description" diff --git a/tests/test_sphinx_pyproject_/test_parse_our_config.yml b/tests/test_sphinx_pyproject_/test_parse_our_config.yml index 098f44b..fb9d4c2 100644 --- a/tests/test_sphinx_pyproject_/test_parse_our_config.yml +++ b/tests/test_sphinx_pyproject_/test_parse_our_config.yml @@ -18,7 +18,7 @@ autodoc_exclude_members: - __getnewargs__ - __abstractmethods__ - __hash__ -copyright: 2021 Dominic Davis-Foster +copyright: 2021-2023 Dominic Davis-Foster description: Move some of your Sphinx configuration into pyproject.toml extensions: - sphinx_toolbox @@ -26,35 +26,35 @@ extensions: - sphinx_toolbox.more_autosummary - sphinx_toolbox.documentation_summary - sphinx_toolbox.tweaks.param_dash +- sphinxcontrib.toctree_plus +- sphinx_toolbox.tweaks.latex_layout - sphinx_toolbox.tweaks.latex_toc - sphinx.ext.intersphinx - sphinx.ext.mathjax -- sphinxcontrib.httpdomain - sphinxcontrib.extras_require - sphinx.ext.todo -- sphinxemoji.sphinxemoji - notfound.extension - sphinx_copybutton - sphinxcontrib.default_values -- sphinxcontrib.toctree_plus - sphinx_debuginfo +- sphinx_licenseinfo - seed_intersphinx_mapping +- html_section +- sphinx_toolbox.more_autosummary.column_widths +- sphinx_toolbox.latex +- sphinx_toolbox.latex.succinct_seealso +- sphinx_toolbox_experimental.missing_xref +- sphinx_toolbox_experimental.changelog +- sphinx_packaging.peps github_repository: sphinx-pyproject github_username: sphinx-toolbox gitstamp_fmt: '%d %b %Y' hide_none_rtype: true -html_context: - conf_py_path: /doc-source/ - display_github: true - github_repo: sphinx-pyproject - github_user: sphinx-toolbox - github_version: master +html_codeblock_linenos_style: table html_show_sourcelink: true html_static_path: - _static html_theme: domdf_sphinx_theme -html_theme_options: - logo_only: false html_theme_path: - ../.. language: en @@ -62,26 +62,24 @@ master_doc: index name: sphinx-pyproject overloads_location: bottom package_root: sphinx_pyproject +project: sphinx-pyproject pygments_style: default source_suffix: .rst -sphinxemoji_style: twemoji suppress_warnings: - image.nonlocal_uri templates_path: - _templates toctree_plus_types: - class -- function -- method +- confval - data +- directive - enum +- exception - flag -- confval -- directive -- role -- confval +- function +- namedtuple - protocol +- role - typeddict -- namedtuple -- exception -version: 0.1.0 +version: 0.3.0 diff --git a/tox.ini b/tox.ini index d6ce96a..560cada 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,15 @@ # * tox # * envlists # * testenv +# * testenv:.package +# * testenv:py313-dev +# * testenv:py313 +# * testenv:py312-dev +# * testenv:py312 # * testenv:docs # * testenv:build # * testenv:lint +# * testenv:perflint # * testenv:mypy # * testenv:pyup # * testenv:coverage @@ -16,84 +22,136 @@ # * pytest [tox] -envlist = py36, py37, py38, py39, py310-dev, pypy36, pypy37, mypy, build +envlist = + py37 + py38 + py39 + py310 + py311 + py312 + py313 + pypy37 + pypy38 + pypy39 + mypy + build skip_missing_interpreters = True isolated_build = True requires = - pip>=20.3.3 + pip>=21,!=22.2 tox-envlist>=0.2.1 - tox-pip-version>=0.0.7 + tox~=3.0 + virtualenv!=20.16.0 [envlists] -test = py36, py37, py38, py39, py310-dev, pypy36, pypy37 +test = py37, py38, py39, py310, py311, py312, py313, pypy37, pypy38, pypy39 qa = mypy, lint -cov = py36, coverage +cov = py39, coverage [testenv] -setenv = PYTHONDEVMODE = 1 +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + SETUPTOOLS_USE_DISTUTILS=stdlib deps = -r{toxinidir}/tests/requirements.txt commands = python --version python -m pytest --cov=sphinx_pyproject -r aR tests/ {posargs} +[testenv:.package] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + +[testenv:py313] +download = True +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + UNSAFE_PYO3_SKIP_VERSION_CHECK=1 + +[testenv:py312] +download = True +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + [testenv:docs] setenv = SHOW_TODOS = 1 +passenv = SPHINX_BUILDER basepython = python3.8 -pip_version = pip>=21 changedir = {toxinidir}/doc-source deps = -r{toxinidir}/doc-source/requirements.txt -commands = sphinx-build -M html . ./build {posargs} +commands = sphinx-build -M {env:SPHINX_BUILDER:html} . ./build {posargs} [testenv:build] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + PIP_PREFER_BINARY=1 + UNSAFE_PYO3_SKIP_VERSION_CHECK=1 skip_install = True changedir = {toxinidir} deps = build[virtualenv]>=0.3.1 check-wheel-contents>=0.1.0 twine>=3.2.0 + cryptography<40; implementation_name == "pypy" and python_version <= "3.7" commands = python -m build --sdist --wheel "{toxinidir}" twine check dist/*.tar.gz dist/*.whl check-wheel-contents dist/ [testenv:lint] -basepython = python3.6 +basepython = python3.9 changedir = {toxinidir} ignore_errors = True skip_install = True deps = - flake8 >=3.8.2 - flake8-2020 >= 1.6.0 + flake8>=3.8.2,<5 + flake8-2020>=1.6.0 flake8-builtins>=1.5.3 flake8-docstrings>=1.5.0 flake8-dunder-all>=0.1.1 flake8-encodings>=0.1.0 flake8-github-actions>=0.1.0 - flake8-pyi>=20.10.0 - flake8-pytest-style>=1.3.0 + flake8-noqa>=1.1.0,<=1.2.2 + flake8-pyi>=20.10.0,<=22.8.0 + flake8-pytest-style>=1.3.0,<2 + flake8-quotes>=3.3.0 flake8-slots>=0.1.0 flake8-sphinx-links>=0.0.4 flake8-strftime>=0.1.1 flake8-typing-imports>=1.10.0 - git+https://github.com/domdfcoding/flake8-quotes.git git+https://github.com/domdfcoding/flake8-rst-docstrings-sphinx.git git+https://github.com/domdfcoding/flake8-rst-docstrings.git - pydocstyle>=6.0.0 + git+https://github.com/python-formate/flake8-unused-arguments.git@magic-methods + git+https://github.com/python-formate/flake8-missing-annotations.git + git+https://github.com/domdfcoding/pydocstyle.git@stub-functions pygments>=2.7.1 + importlib_metadata<4.5.0; python_version<'3.8' commands = python3 -m flake8_rst_docstrings_sphinx sphinx_pyproject tests --allow-toolbox {posargs} +[testenv:perflint] +basepython = python3.9 +changedir = {toxinidir} +ignore_errors = True +skip_install = True +deps = perflint +commands = python3 -m perflint sphinx_pyproject {posargs} + [testenv:mypy] -basepython = python3.6 +basepython = python3.9 ignore_errors = True changedir = {toxinidir} deps = - mypy==0.812 + mypy==1.16 -r{toxinidir}/tests/requirements.txt -r{toxinidir}/stubs.txt commands = mypy sphinx_pyproject tests {posargs} [testenv:pyup] -basepython = python3.6 +basepython = python3.9 skip_install = True ignore_errors = True changedir = {toxinidir} @@ -101,10 +159,15 @@ deps = pyupgrade-directories commands = pyup_dirs sphinx_pyproject tests --py36-plus --recursive [testenv:coverage] -basepython = python3.6 +basepython = python3.9 skip_install = True ignore_errors = True whitelist_externals = /bin/bash +passenv = + COV_PYTHON_VERSION + COV_PLATFORM + COV_PYTHON_IMPLEMENTATION + * changedir = {toxinidir} deps = coverage>=5 @@ -116,12 +179,18 @@ commands = [flake8] max-line-length = 120 -select = E111 E112 E113 E121 E122 E125 E127 E128 E129 E131 E133 E201 E202 E203 E211 E222 E223 E224 E225 E225 E226 E227 E228 E231 E241 E242 E251 E261 E262 E265 E271 E272 E303 E304 E306 E402 E502 E703 E711 E712 E713 E714 E721 W291 W292 W293 W391 W504 YTT101 YTT102 YTT103 YTT201 YTT202 YTT203 YTT204 YTT301 YTT302 YTT303 STRFTIME001 STRFTIME002 SXL001 PT001 PT002 PT003 PT006 PT007 PT008 PT009 PT010 PT011 PT012 PT013 PT014 PT015 PT016 PT017 PT018 PT019 PT020 PT021 RST201 RST202 RST203 RST204 RST205 RST206 RST207 RST208 RST210 RST211 RST212 RST213 RST214 RST215 RST216 RST217 RST218 RST219 RST299 RST301 RST302 RST303 RST304 RST305 RST306 RST399 RST401 RST499 RST900 RST901 RST902 RST903 Q001 Q002 Q003 A001 A002 A003 TYP001 TYP002 TYP003 TYP004 TYP005 TYP006 ENC001 ENC002 ENC003 ENC004 ENC011 ENC012 ENC021 ENC022 ENC023 ENC024 ENC025 ENC026 Y001,Y002 Y003 Y004 Y005 Y006 Y007 Y008 Y009 Y010 Y011 Y012 Y013 Y014 Y015 Y090 Y091 E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 +select = E111 E112 E113 E121 E122 E125 E127 E128 E129 E131 E133 E201 E202 E203 E211 E222 E223 E224 E225 E225 E226 E227 E228 E231 E241 E242 E251 E261 E262 E265 E271 E272 E303 E304 E306 E402 E502 E703 E711 E712 E713 E714 E721 W291 W292 W293 W391 W504 YTT101 YTT102 YTT103 YTT201 YTT202 YTT203 YTT204 YTT301 YTT302 YTT303 STRFTIME001 STRFTIME002 SXL001 PT001 PT002 PT003 PT006 PT007 PT008 PT009 PT010 PT011 PT012 PT013 PT014 PT015 PT016 PT017 PT018 PT019 PT020 PT021 RST201 RST202 RST203 RST204 RST205 RST206 RST207 RST208 RST210 RST211 RST212 RST213 RST214 RST215 RST216 RST217 RST218 RST219 RST299 RST301 RST302 RST303 RST304 RST305 RST306 RST399 RST401 RST499 RST900 RST901 RST902 RST903 Q001 Q002 Q003 A001 A002 TYP001 TYP002 TYP003 TYP004 TYP005 TYP006 ENC001 ENC002 ENC003 ENC004 ENC011 ENC012 ENC021 ENC022 ENC023 ENC024 ENC025 ENC026 Y001,Y002 Y003 Y004 Y005 Y006 Y007 Y008 Y009 Y010 Y011 Y012 Y013 Y014 Y015 Y090 Y091 NQA001 NQA002 NQA003 NQA004 NQA005 NQA102 NQA103 E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 extend-exclude = doc-source,old,build,dist,__pkginfo__.py,setup.py,venv rst-directives = TODO + autosummary-widths envvar extras-require + license + license-info +rst-roles = + choosealicense + pep621 per-file-ignores = tests/*: D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 */*.pyi: E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 @@ -130,20 +199,25 @@ inline-quotes = " multiline-quotes = """ docstring-quotes = """ count = True -min_python_version = 3.6.1 +min_python_version = 3.7 +unused-arguments-ignore-abstract-functions = True +unused-arguments-ignore-overload-functions = True +unused-arguments-ignore-magic-methods = True +unused-arguments-ignore-variadic-names = True [coverage:run] plugins = coverage_pyver_pragma [coverage:report] -fail_under = 80 +fail_under = 100 +show_missing = True exclude_lines = raise AssertionError raise NotImplementedError if 0: if False: - if TYPE_CHECKING: - if typing.TYPE_CHECKING: + if TYPE_CHECKING + if typing.TYPE_CHECKING if __name__ == .__main__.: [check-wheel-contents]