├── tests ├── source │ ├── _static │ │ └── .gitkeep │ ├── index.rst │ ├── examples.rst │ └── conf.py └── test_sphinx_issues.py ├── .github ├── dependabot.yml └── workflows │ └── build-release.yml ├── RELEASING.md ├── tox.ini ├── CONTRIBUTING.md ├── .pre-commit-config.yaml ├── .gitignore ├── LICENSE ├── NOTICE ├── pyproject.toml ├── README.rst └── src └── sphinx_issues └── __init__.py /tests/source/_static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Welcome to sphinx-issues's documentation! 3 | ========================================= 4 | 5 | 6 | .. include:: examples.rst 7 | .. include:: ../README.rst 8 | -------------------------------------------------------------------------------- /tests/source/examples.rst: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | - See issues :issue:`12,13` 4 | 5 | - See other issues :issue:`sloria/konch#45,46`. 6 | 7 | - See PR :pr:`58`, thanks :user:`kound` 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | 1. Bump version in `pyproject.toml` and update the changelog 4 | with today's date. 5 | 2. Commit: `git commit -m "Bump version and update changelog"` 6 | 3. Tag the commit: `git tag x.y.z` 7 | 4. Push: `git push --tags origin main`. CI will take care of the 8 | PyPI release. 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = lint,py{39,310,311,312,313} 3 | 4 | [testenv] 5 | extras = tests 6 | commands = pytest {posargs} 7 | 8 | [testenv:lint] 9 | deps = pre-commit~=3.6 10 | skip_install = true 11 | commands = pre-commit run --all-files 12 | 13 | ; Below tasks are for development only (not run in CI) 14 | [testenv:watch-readme] 15 | deps = restview 16 | skip_install = true 17 | commands = restview README.rst 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Setting up for development 4 | 5 | - Create and activate a new virtual environment 6 | - `pip install -e '.[dev]'` 7 | - (Optional but recommended) Install the pre-commit hooks, which will 8 | format and lint your git staged files: 9 | 10 | ``` 11 | # The pre-commit CLI was installed above 12 | pre-commit install 13 | ``` 14 | 15 | - To run tests: 16 | 17 | ``` 18 | pytest 19 | ``` 20 | 21 | - To run syntax checks: 22 | 23 | ``` 24 | tox -e lint 25 | ``` 26 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_schedule: monthly 3 | repos: 4 | - repo: https://github.com/astral-sh/ruff-pre-commit 5 | rev: v0.14.7 6 | hooks: 7 | - id: ruff 8 | - id: ruff-format 9 | - repo: https://github.com/python-jsonschema/check-jsonschema 10 | rev: 0.35.0 11 | hooks: 12 | - id: check-github-workflows 13 | - id: check-readthedocs 14 | - repo: https://github.com/asottile/blacken-docs 15 | rev: 1.20.0 16 | hooks: 17 | - id: blacken-docs 18 | additional_dependencies: [black==23.12.1] 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | # Complexity 38 | output/*.html 39 | output/*/index.html 40 | 41 | # Sphinx 42 | docs/_build 43 | README.html 44 | 45 | # mypy 46 | .mypy_cache/ 47 | 48 | # ruff 49 | .ruff_cache 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Steven Loria and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | sphinx-issues includes code adapted from other libraries. Their licenses are included here. 2 | 3 | releases License 4 | ================ 5 | 6 | Copyright (c) 2014, Jeff Forcier 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sphinx-issues" 3 | version = "5.0.1" 4 | description = "A Sphinx extension for linking to your project's issue tracker" 5 | readme = "README.rst" 6 | license = { file = "LICENSE" } 7 | authors = [{ name = "Steven Loria", email = "sloria1@gmail.com" }] 8 | classifiers = [ 9 | "Framework :: Sphinx :: Extension", 10 | "Intended Audience :: Developers", 11 | "License :: OSI Approved :: MIT License", 12 | "Programming Language :: Python :: 3", 13 | "Programming Language :: Python :: 3.9", 14 | "Programming Language :: Python :: 3.10", 15 | "Programming Language :: Python :: 3.11", 16 | "Programming Language :: Python :: 3.12", 17 | "Programming Language :: Python :: 3.13", 18 | "Topic :: Software Development :: Documentation", 19 | ] 20 | keywords = ["sphinx", "issues", "github"] 21 | requires-python = ">=3.9" 22 | dependencies = ["sphinx"] 23 | 24 | [project.urls] 25 | Issues = "https://github.com/sloria/sphinx-issues/issues" 26 | Source = "https://github.com/sloria/sphinx-issues" 27 | 28 | [project.optional-dependencies] 29 | tests = ["pytest"] 30 | dev = ["sphinx-issues[tests]", "tox", "pre-commit>=3.6,<5.0"] 31 | 32 | [build-system] 33 | requires = ["flit_core<4"] 34 | build-backend = "flit_core.buildapi" 35 | 36 | [tool.flit.sdist] 37 | include = ["tests/", "CONTRIBUTING.md", "NOTICE", "tox.ini"] 38 | exclude = ["docs/_build/"] 39 | 40 | [tool.ruff] 41 | src = ["src"] 42 | fix = true 43 | show-fixes = true 44 | output-format = "full" 45 | 46 | [tool.ruff.format] 47 | docstring-code-format = true 48 | 49 | [tool.ruff.lint] 50 | ignore = ["E203", "E266", "E501", "E731"] 51 | select = [ 52 | "B", # flake8-bugbear 53 | "E", # pycodestyle error 54 | "F", # pyflakes 55 | "I", # isort 56 | "UP", # pyupgrade 57 | "W", # pycodestyle warning 58 | ] 59 | 60 | [tool.pytest.ini_options] 61 | filterwarnings = [ 62 | "ignore:is already registered, its visitors will be overridden:UserWarning", 63 | ] 64 | -------------------------------------------------------------------------------- /tests/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = "sphinx-issues" 21 | copyright = "2022, foobar" 22 | author = "foobar" 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = ["sphinx_issues"] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ["_templates"] 34 | 35 | # List of patterns, relative to source directory, that match files and 36 | # directories to ignore when looking for source files. 37 | # This pattern also affects html_static_path and html_extra_path. 38 | exclude_patterns = [] 39 | 40 | 41 | # -- Options for HTML output ------------------------------------------------- 42 | 43 | # The theme to use for HTML and HTML Help pages. See the documentation for 44 | # a list of builtin themes. 45 | # 46 | html_theme = "alabaster" 47 | 48 | # Add any paths that contain custom static files (such as style sheets) here, 49 | # relative to this directory. They are copied after the builtin static files, 50 | # so a file named "default.css" will overwrite the builtin "default.css". 51 | html_static_path = ["_static"] 52 | 53 | # 54 | suppress_warnings = ["app.add_node"] 55 | 56 | issues_uri = "https://gitlab.company.com/{group}/{project}/-/issues/{issue}" 57 | issues_prefix = "#" 58 | issues_pr_uri = "https://gitlab.company.com/{group}/{project}/-/merge_requests/{pr}" 59 | issues_pr_prefix = "!" 60 | issues_commit_uri = "https://gitlab.company.com/{group}/{project}/-/commit/{commit}" 61 | issues_commit_prefix = "@" 62 | issues_user_uri = "https://gitlab.company.com/{user}" 63 | issues_user_prefix = "@" 64 | issues_default_group_project = "myteam/super_great_project" 65 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: ["main"] 5 | tags: ["*"] 6 | pull_request: 7 | 8 | jobs: 9 | tests: 10 | name: ${{ matrix.name }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - { name: "3.9", python: "3.9", tox: py39 } 17 | - { name: "3.13", python: "3.13", tox: py313 } 18 | steps: 19 | - uses: actions/checkout@v6 20 | - uses: actions/setup-python@v6 21 | with: 22 | python-version: ${{ matrix.python }} 23 | allow-prereleases: true 24 | - run: python -m pip install --upgrade pip 25 | - run: python -m pip install tox 26 | - run: python -m tox -e ${{ matrix.tox }} 27 | build: 28 | name: Build package 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: actions/setup-python@v6 33 | with: 34 | python-version: "3.13" 35 | - name: Install pypa/build 36 | run: python -m pip install build 37 | - name: Build a binary wheel and a source tarball 38 | run: python -m build 39 | - name: Install twine 40 | run: python -m pip install twine 41 | - name: Check build 42 | run: python -m twine check --strict dist/* 43 | - name: Store the distribution packages 44 | uses: actions/upload-artifact@v5 45 | with: 46 | name: python-package-distributions 47 | path: dist/ 48 | # this duplicates pre-commit.ci, so only run it on tags 49 | # it guarantees that linting is passing prior to a release 50 | lint-pre-release: 51 | if: startsWith(github.ref, 'refs/tags') 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v6 55 | - uses: actions/setup-python@v6 56 | with: 57 | python-version: "3.13" 58 | - run: python -m pip install tox 59 | - run: python -m tox -e lint 60 | publish-to-pypi: 61 | name: PyPI release 62 | if: startsWith(github.ref, 'refs/tags/') 63 | needs: [build, tests, lint-pre-release] 64 | runs-on: ubuntu-latest 65 | environment: 66 | name: pypi 67 | url: https://pypi.org/p/sphinx-issues 68 | permissions: 69 | id-token: write 70 | steps: 71 | - name: Download all the dists 72 | uses: actions/download-artifact@v6 73 | with: 74 | name: python-package-distributions 75 | path: dist/ 76 | - name: Publish distribution to PyPI 77 | uses: pypa/gh-action-pypi-publish@release/v1 78 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | sphinx-issues 3 | ============= 4 | 5 | .. image:: https://badgen.net/pypi/v/sphinx-issues 6 | :target: https://pypi.org/project/sphinx-issues/ 7 | :alt: PyPI badge 8 | 9 | .. image:: https://github.com/sloria/sphinx-issues/actions/workflows/build-release.yml/badge.svg 10 | :target: https://github.com/sloria/sphinx-issues/actions/workflows/build-release.yml 11 | :alt: Build status 12 | 13 | A Sphinx extension for linking to your project's issue tracker. Includes roles for linking to issues, pull requests, user profiles, with built-in support for GitHub (though this works with other services). 14 | 15 | Example 16 | ******* 17 | 18 | For an example usage, check out `marshmallow's changelog `_, which makes use of the roles in this library. 19 | 20 | Installation and Configuration 21 | ****************************** 22 | 23 | .. code-block:: console 24 | 25 | pip install sphinx-issues 26 | 27 | 28 | Add ``sphinx_issues`` to ``extensions`` in your ``conf.py``. 29 | 30 | The extension has default values for GitHub projects. 31 | Add the ``issues_github_path`` config variable and you are good 32 | to go: 33 | 34 | .. code-block:: python 35 | 36 | # docs/conf.py 37 | 38 | # ... 39 | extensions = [ 40 | # ... 41 | "sphinx_issues" 42 | ] 43 | 44 | # Path to GitHub repo {group}/{project} (note that `group` is the GitHub user or organization) 45 | issues_github_path = "sloria/marshmallow" 46 | 47 | # which is the equivalent to: 48 | issues_uri = "https://github.com/{group}/{project}/issues/{issue}" 49 | issues_prefix = "#" 50 | issues_pr_uri = "https://github.com/{group}/{project}/pull/{pr}" 51 | issues_pr_prefix = "#" 52 | issues_commit_uri = "https://github.com/{group}/{project}/commit/{commit}" 53 | issues_commit_prefix = "@" 54 | issues_user_uri = "https://github.com/{user}" 55 | issues_user_prefix = "@" 56 | 57 | You can also use this extension with other issue trackers. Here is how you could configure it for a hosted GitLab instance: 58 | 59 | .. code-block:: python 60 | 61 | # docs/conf.py 62 | 63 | # ... 64 | extensions = [ 65 | # ... 66 | "sphinx_issues" 67 | ] 68 | 69 | # Default repo {group}/{project} of gitlab project 70 | issues_default_group_project = "myteam/super_great_project" 71 | issues_uri = "https://gitlab.company.com/{group}/{project}/-/issues/{issue}" 72 | issues_prefix = "#" 73 | issues_pr_uri = "https://gitlab.company.com/{group}/{project}/-/merge_requests/{pr}" 74 | issues_pr_prefix = "!" 75 | issues_commit_uri = "https://gitlab.company.com/{group}/{project}/-/commit/{commit}" 76 | issues_commit_prefix = "@" 77 | issues_user_uri = "https://gitlab.company.com/{user}" 78 | issues_user_prefix = "@" 79 | 80 | 81 | Usage inside the documentation 82 | ****************************** 83 | 84 | Use the ``:issue:`` and ``:pr:`` roles in your docs like so: 85 | 86 | .. code-block:: rst 87 | 88 | See issue :issue:`42` 89 | 90 | See issues :issue:`12,13` 91 | 92 | See :issue:`sloria/konch#45`. 93 | 94 | See PR :pr:`58` 95 | 96 | 97 | The ``:user:`` role links to user profiles (GitHub by default, but can be configured via the ``issues_user_uri`` config variable). 98 | 99 | The ``:commit:`` role links to commits. 100 | 101 | .. code-block:: rst 102 | 103 | Fixed in :commit:`6bb9124d5e9dbb2f7b52864c3d8af7feb1b69403`. 104 | 105 | .. code-block:: rst 106 | 107 | Thanks to :user:`bitprophet` for the idea! 108 | 109 | You can also change the text of the hyperlink: 110 | 111 | .. code-block:: rst 112 | 113 | This change is due to :user:`Andreas Mueller `. 114 | 115 | The syntax ``:role:`My custom title ``` works for all roles of this extension. 116 | 117 | .. code-block:: rst 118 | 119 | Fix bad bug :issue:`123, 199 (Duplicate) <123>` 120 | 121 | The ``:pypi:`` role links to project pages on `PyPI `_. 122 | 123 | .. code-block:: rst 124 | 125 | :pypi:`sphinx-issues` - A Sphinx extension for linking to your project's issue tracker. 126 | 127 | Important note about :cwe: and :cve: roles 128 | ****************************************** 129 | 130 | The ``:cwe:`` and ``:cve:`` are included within `newer versions of Sphinx `_. 131 | If you use these roles and are using Sphinx<8.1, you will need to 132 | install sphinx-issues<5. 133 | 134 | Credits 135 | ******* 136 | 137 | Credit goes to Jeff Forcier for his work on the `releases `_ extension, which is a full-featured solution for generating changelogs. I just needed a quick way to reference GitHub issues in my docs, so I yoinked the bits that I needed. 138 | 139 | License 140 | ******* 141 | 142 | MIT licensed. See the bundled `LICENSE `_ file for more details. 143 | 144 | 145 | Changelog 146 | ********* 147 | 148 | 5.0.1 (2025-04-10) 149 | ------------------ 150 | 151 | - Properly handle user and org names with symbols in them (`#174 `_). 152 | Thanks @ilan-gold for reporting and @flying-sheep for the PR. 153 | 154 | 5.0.0 (2024-10-11) 155 | ------------------ 156 | 157 | - Remove `:cwe:` and `:cve:` roles, as these are officially included in Sphinx>=8.1.0. 158 | - Support Python 3.9-3.13. Python 3.8 is no longer supported. 159 | 160 | 4.1.0 (2024-04-14) 161 | ------------------ 162 | 163 | - Add `:pypi:` role for linking to PyPI projects (`#144 `_). 164 | Thanks @shenxianpeng for the suggestion and PR. 165 | 166 | 4.0.0 (2024-01-19) 167 | ------------------ 168 | 169 | - Default to linking GH Sponsors for the :user: role (`#129 `_). 170 | Thanks @webknjaz for the suggestion. 171 | - Support Python 3.8-3.12. Older versions are no longer supported. 172 | - *Backwards-incompatible*: Remove ``__version__``, ``__author__``, and ``__license__`` attributes. 173 | Use ``importlib.metadata`` to read this metadata instead. 174 | 175 | 3.0.1 (2022-01-11) 176 | ------------------ 177 | 178 | - Fix regression from 3.0.0: `exception: 'in ' requires string as left operand, not type`. 179 | 180 | 3.0.0 (2022-01-10) 181 | ------------------ 182 | 183 | - The `:commit:` role now outputs with an `@` prefix. 184 | - Add configuration options for changing prefixes. 185 | - Allow `{group}` to be specified within `issues_uri`, `issues_pr_uri`, `issues_commit_uri`, and 186 | 187 | 2.0.0 (2022-01-01) 188 | ------------------ 189 | 190 | - Drop support for Python 2.7 and 3.5. 191 | - Test against Python 3.8 to 3.10. 192 | - Add ``:cwe:`` role for linking to CVEs on https://cwe.mitre.org. 193 | Thanks @hugovk for the PR. 194 | - Add support for custom urls and separators `Issue #93 `_ 195 | - Allow custom titles for all roles `Issue #116 `_ 196 | - Added setting `issues_default_group_project` as future replacement of `issues_github_path`, to reflect the now to universal nature of the extension 197 | 198 | 1.2.0 (2018-12-26) 199 | ------------------ 200 | 201 | - Add ``:commit:`` role for linking to commits. 202 | - Add support for linking to external repos. 203 | - Test against Python 3.7. 204 | 205 | 1.1.0 (2018-09-18) 206 | ------------------ 207 | 208 | - Add ``:cve:`` role for linking to CVEs on https://cve.mitre.org. 209 | 210 | 1.0.0 (2018-07-14) 211 | ------------------ 212 | 213 | - Add ``:pr:`` role. Thanks @jnotham for the suggestion. 214 | - Drop support for Python 3.4. 215 | 216 | 0.4.0 (2017-11-25) 217 | ------------------ 218 | 219 | - Raise ``ValueError`` if neither ``issues_uri`` nor ``issues_github_path`` is set. Thanks @jnothman for the PR. 220 | - Drop support for Python 2.6 and 3.3. 221 | 222 | 0.3.1 (2017-01-16) 223 | ------------------ 224 | 225 | - ``setup`` returns metadata, preventing warnings about parallel reads and writes. Thanks @jfinkels for reporting. 226 | 227 | 0.3.0 (2016-10-20) 228 | ------------------ 229 | 230 | - Support anchor text for ``:user:`` role. Thanks @jnothman for the suggestion and thanks @amueller for the PR. 231 | 232 | 0.2.0 (2014-12-22) 233 | ------------------ 234 | 235 | - Add ``:user:`` role for linking to GitHub user profiles. 236 | 237 | 0.1.0 (2014-12-21) 238 | ------------------ 239 | 240 | - Initial release. 241 | -------------------------------------------------------------------------------- /tests/test_sphinx_issues.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | from pathlib import Path 4 | from shutil import rmtree 5 | from tempfile import mkdtemp 6 | from unittest.mock import Mock 7 | 8 | import pytest 9 | import sphinx.application 10 | 11 | from sphinx_issues import ( 12 | commit_role, 13 | issue_role, 14 | pr_role, 15 | pypi_role, 16 | user_role, 17 | ) 18 | from sphinx_issues import setup as issues_setup 19 | 20 | BASE_DIR = Path(__file__).parent.absolute() 21 | 22 | 23 | @pytest.fixture( 24 | params=[ 25 | # Parametrize config 26 | {"issues_github_path": "marshmallow-code/marshmallow"}, 27 | {"issues_default_group_project": "marshmallow-code/marshmallow"}, 28 | { 29 | "issues_uri": "https://github.com/marshmallow-code/marshmallow/issues/{issue}", 30 | "issues_pr_uri": "https://github.com/marshmallow-code/marshmallow/pull/{pr}", 31 | "issues_commit_uri": "https://github.com/marshmallow-code/marshmallow/commit/{commit}", 32 | }, 33 | ] 34 | ) 35 | def app(request): 36 | src, doctree, confdir, outdir = (mkdtemp() for _ in range(4)) 37 | sphinx.application.Sphinx._log = lambda self, message, wfile, nonl=False: None 38 | app = sphinx.application.Sphinx( 39 | srcdir=src, confdir=None, outdir=outdir, doctreedir=doctree, buildername="html" 40 | ) 41 | issues_setup(app) 42 | # Stitch together as the sphinx app init() usually does w/ real conf files 43 | app.config._raw_config = request.param 44 | try: 45 | app.config.init_values() 46 | except TypeError: 47 | app.config.init_values(lambda x: x) 48 | yield app 49 | [rmtree(x) for x in (src, doctree, confdir, outdir)] 50 | 51 | 52 | @pytest.fixture() 53 | def inliner(app): 54 | return Mock(document=Mock(settings=Mock(env=Mock(app=app)))) 55 | 56 | 57 | @pytest.mark.parametrize( 58 | ("role", "role_name", "text", "expected_text", "expected_url"), 59 | [ 60 | ( 61 | issue_role, 62 | "issue", 63 | "42", 64 | "#42", 65 | "https://github.com/marshmallow-code/marshmallow/issues/42", 66 | ), 67 | ( 68 | issue_role, 69 | "issue", 70 | "Hard Issue <42>", 71 | "Hard Issue", 72 | "https://github.com/marshmallow-code/marshmallow/issues/42", 73 | ), 74 | ( 75 | issue_role, 76 | "issue", 77 | "Issue #17 (the big one) <17>", 78 | "Issue #17 (the big one)", 79 | "https://github.com/marshmallow-code/marshmallow/issues/17", 80 | ), 81 | ( 82 | issue_role, 83 | "issue", 84 | "Not my business ", 85 | "Not my business", 86 | "https://github.com/foo/bar/issues/42", 87 | ), 88 | ( 89 | pr_role, 90 | "pr", 91 | "42", 92 | "#42", 93 | "https://github.com/marshmallow-code/marshmallow/pull/42", 94 | ), 95 | (user_role, "user", "sloria", "@sloria", "https://github.com/sponsors/sloria"), 96 | ( 97 | user_role, 98 | "user", 99 | "Steven Loria ", 100 | "Steven Loria", 101 | "https://github.com/sponsors/sloria", 102 | ), 103 | ( 104 | pypi_role, 105 | "pypi", 106 | "sphinx-issues", 107 | "sphinx-issues", 108 | "https://pypi.org/project/sphinx-issues", 109 | ), 110 | ( 111 | commit_role, 112 | "commit", 113 | "123abc456def", 114 | "@123abc4", 115 | "https://github.com/marshmallow-code/marshmallow/commit/123abc456def", 116 | ), 117 | ( 118 | commit_role, 119 | "commit", 120 | "1.0.0 (2016-07-05) <170ce9>", 121 | "1.0.0 (2016-07-05)", 122 | "https://github.com/marshmallow-code/marshmallow/commit/170ce9", 123 | ), 124 | # External issue 125 | ( 126 | issue_role, 127 | "issue", 128 | "slo-ria/web-args#42", 129 | "slo-ria/web-args#42", 130 | "https://github.com/slo-ria/web-args/issues/42", 131 | ), 132 | # External PR 133 | ( 134 | pr_role, 135 | "pr", 136 | "slo-ria/web-args#42", 137 | "slo-ria/web-args#42", 138 | "https://github.com/slo-ria/web-args/pull/42", 139 | ), 140 | # External commit 141 | ( 142 | commit_role, 143 | "commit", 144 | "slo-ria/web-args@abc123def456", 145 | "slo-ria/web-args@abc123d", 146 | "https://github.com/slo-ria/web-args/commit/abc123def456", 147 | ), 148 | ], 149 | ) 150 | def test_roles(inliner, role, role_name, text, expected_text, expected_url): 151 | result = role(role_name, rawtext="", text=text, lineno=None, inliner=inliner) 152 | link = result[0][0] 153 | assert link.astext() == expected_text 154 | assert link.attributes["refuri"] == expected_url 155 | 156 | 157 | def test_issue_role_multiple(inliner): 158 | result = issue_role( 159 | name=None, rawtext="", text="a title <42>,43", inliner=inliner, lineno=None 160 | ) 161 | link1 = result[0][0] 162 | assert link1.astext() == "a title" 163 | issue_url = "https://github.com/marshmallow-code/marshmallow/issues/" 164 | assert link1.attributes["refuri"] == issue_url + "42" 165 | 166 | sep = result[0][1] 167 | assert sep.astext() == ", " 168 | 169 | link2 = result[0][2] 170 | assert link2.astext() == "#43" 171 | assert link2.attributes["refuri"] == issue_url + "43" 172 | 173 | 174 | def test_issue_role_multiple_with_external(inliner): 175 | result = issue_role( 176 | "issue", rawtext="", text="42,sloria/konch#43", inliner=inliner, lineno=None 177 | ) 178 | link1 = result[0][0] 179 | assert link1.astext() == "#42" 180 | issue_url = "https://github.com/marshmallow-code/marshmallow/issues/42" 181 | assert link1.attributes["refuri"] == issue_url 182 | 183 | sep = result[0][1] 184 | assert sep.astext() == ", " 185 | 186 | link2 = result[0][2] 187 | assert link2.astext() == "sloria/konch#43" 188 | assert link2.attributes["refuri"] == "https://github.com/sloria/konch/issues/43" 189 | 190 | 191 | @pytest.fixture 192 | def app_custom_uri(): 193 | src, doctree, confdir, outdir = (mkdtemp() for _ in range(4)) 194 | sphinx.application.Sphinx._log = lambda self, message, wfile, nonl=False: None 195 | app = sphinx.application.Sphinx( 196 | srcdir=src, confdir=None, outdir=outdir, doctreedir=doctree, buildername="html" 197 | ) 198 | issues_setup(app) 199 | # Stitch together as the sphinx app init() usually does w/ real conf files 200 | app.config._raw_config = { 201 | "issues_default_group_project": "myteam/super_great_project", 202 | "issues_uri": "https://gitlab.company.com/{group}/{project}/-/issues/{issue}", 203 | "issues_prefix": "#", 204 | "issues_pr_uri": "https://gitlab.company.com/{group}/{project}/-/merge_requests/{pr}", 205 | "issues_pr_prefix": "!", 206 | "issues_commit_uri": "https://gitlab.company.com/{group}/{project}/-/commit/{commit}", 207 | "issues_commit_prefix": "@", 208 | "issues_user_uri": "https://gitlab.company.com/{user}", 209 | "issues_user_prefix": "@", 210 | } 211 | try: 212 | app.config.init_values() 213 | except TypeError: 214 | app.config.init_values(lambda x: x) 215 | yield app 216 | [rmtree(x) for x in (src, doctree, confdir, outdir)] 217 | 218 | 219 | @pytest.fixture() 220 | def inliner_custom_uri(app_custom_uri): 221 | return Mock(document=Mock(settings=Mock(env=Mock(app=app_custom_uri)))) 222 | 223 | 224 | @pytest.mark.parametrize( 225 | ("role", "role_name", "text", "expected_text", "expected_url"), 226 | [ 227 | ( 228 | issue_role, 229 | "issue", 230 | "42", 231 | "#42", 232 | "https://gitlab.company.com/myteam/super_great_project/-/issues/42", 233 | ), 234 | ( 235 | issue_role, 236 | "issue", 237 | "Hard Issue <42>", 238 | "Hard Issue", 239 | "https://gitlab.company.com/myteam/super_great_project/-/issues/42", 240 | ), 241 | ( 242 | issue_role, 243 | "issue", 244 | "Not my business ", 245 | "Not my business", 246 | "https://gitlab.company.com/foo/bar/-/issues/42", 247 | ), 248 | ( 249 | pr_role, 250 | "pr", 251 | "42", 252 | "!42", 253 | "https://gitlab.company.com/myteam/super_great_project/-/merge_requests/42", 254 | ), 255 | (user_role, "user", "sloria", "@sloria", "https://gitlab.company.com/sloria"), 256 | ( 257 | user_role, 258 | "user", 259 | "Steven Loria ", 260 | "Steven Loria", 261 | "https://gitlab.company.com/sloria", 262 | ), 263 | ( 264 | commit_role, 265 | "commit", 266 | "123abc456def", 267 | "@123abc4", 268 | "https://gitlab.company.com/myteam/super_great_project/-/commit/123abc456def", 269 | ), 270 | # External issue 271 | ( 272 | issue_role, 273 | "issue", 274 | "sloria/webargs#42", 275 | "sloria/webargs#42", 276 | "https://gitlab.company.com/sloria/webargs/-/issues/42", 277 | ), 278 | # External PR 279 | ( 280 | pr_role, 281 | "pr", 282 | "sloria/webargs#42", 283 | "sloria/webargs!42", 284 | "https://gitlab.company.com/sloria/webargs/-/merge_requests/42", 285 | ), 286 | # External commit 287 | ( 288 | commit_role, 289 | "commit", 290 | "sloria/webargs@abc123def456", 291 | "sloria/webargs@abc123d", 292 | "https://gitlab.company.com/sloria/webargs/-/commit/abc123def456", 293 | ), 294 | ], 295 | ) 296 | def test_roles_custom_uri( 297 | inliner_custom_uri, role, role_name, text, expected_text, expected_url 298 | ): 299 | result = role( 300 | role_name, rawtext="", text=text, lineno=None, inliner=inliner_custom_uri 301 | ) 302 | link = result[0][0] 303 | assert link.astext() == expected_text 304 | assert link.attributes["refuri"] == expected_url 305 | 306 | 307 | @pytest.fixture 308 | def tmp_doc_build_folder(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: 309 | """Generate a temporary source folder and chdir in it. Return the build folder""" 310 | source = tmp_path / "source" 311 | build = tmp_path / "build" 312 | static = source / "_static" 313 | for folder in (source, build, static): 314 | folder.mkdir() 315 | conf_py = BASE_DIR / "source" / "conf.py" 316 | examples_rst = BASE_DIR / "source" / "examples.rst" 317 | 318 | source.joinpath("conf.py").write_bytes(conf_py.read_bytes()) 319 | source.joinpath("index.rst").write_bytes(examples_rst.read_bytes()) 320 | 321 | monkeypatch.chdir(source) 322 | return build 323 | 324 | 325 | def test_sphinx_build_integration(tmp_doc_build_folder: Path): 326 | """Ensure that a simulated complete sphinx run works as expected""" 327 | subprocess.run( 328 | [ 329 | Path(sys.executable).parent.joinpath("sphinx-build"), 330 | "-b", 331 | "html", 332 | "-W", # turn warnings into errors 333 | "-E", # force rebuild of environment (even if we work in tmp) 334 | ".", 335 | str(tmp_doc_build_folder), 336 | ], 337 | check=True, 338 | ) 339 | 340 | created = tmp_doc_build_folder / "index.html" 341 | assert created.exists() and created.is_file() 342 | content = created.read_text() 343 | issue_url = "https://gitlab.company.com/myteam/super_great_project/-/issues/" 344 | other_issue_url = "https://gitlab.company.com/sloria/konch/-/issues/" 345 | pr_url = "https://gitlab.company.com/myteam/super_great_project/-/merge_requests/" 346 | user_url = "https://gitlab.company.com/" 347 | 348 | # We could do something fancy like an HTML parser or regex: 349 | # Instead we keep it simple 350 | expected_strings = ( 351 | ( 352 | f"See issues " 353 | f'#12, ' 354 | f'#13' 355 | ), 356 | ( 357 | f"See other issues " 358 | f'sloria/konch#45,' 359 | f' #46' 360 | ), 361 | ( 362 | f'See PR !58, ' 363 | f'thanks @kound' 364 | ), 365 | ) 366 | # Ensure that we do no check character wise but line wise 367 | assert len(expected_strings) == 3 368 | 369 | for expected in expected_strings: 370 | assert expected in content 371 | -------------------------------------------------------------------------------- /src/sphinx_issues/__init__.py: -------------------------------------------------------------------------------- 1 | """A Sphinx extension for linking to your project's issue tracker.""" 2 | 3 | import importlib.metadata 4 | import re 5 | from typing import Callable, Optional 6 | 7 | from docutils import nodes, utils 8 | from sphinx.config import Config 9 | from sphinx.util.nodes import split_explicit_title 10 | 11 | GITHUB_USER_RE = re.compile("^https://github.com/([^/]+)/([^/]+)/.*") 12 | 13 | 14 | def _get_default_group_and_project( 15 | config: Config, uri_config_option: str 16 | ) -> Optional[tuple[str, str]]: 17 | """ 18 | Get the default group/project or None if not set 19 | """ 20 | old_config = getattr(config, "issues_github_path", None) 21 | new_config = getattr(config, "issues_default_group_project", None) 22 | 23 | if old_config and new_config: 24 | raise ValueError( 25 | "Both 'issues_github_path' and 'issues_default_group_project' are set, even" 26 | " though they define the same setting. " 27 | "Please only define one of these." 28 | ) 29 | group_and_project = new_config or old_config 30 | 31 | if group_and_project: 32 | assert isinstance(group_and_project, str) 33 | try: 34 | group, project = group_and_project.split("/", maxsplit=1) 35 | return group, project 36 | except ValueError as e: 37 | raise ValueError( 38 | "`issues_github_path` or `issues_default_group_project` needs to " 39 | "define a value in the form of `/` " 40 | f"but `{config}` was given." 41 | ) from e 42 | 43 | # If group and project was not set, we need to look for it within the github url 44 | # for backward compatibility 45 | if not group_and_project: 46 | uri = getattr(config, uri_config_option) 47 | if uri: 48 | match = GITHUB_USER_RE.match(uri) 49 | if match: 50 | return match.groups()[0], match.groups()[1] 51 | 52 | return None 53 | 54 | 55 | def _get_placeholder(uri_config_option: str) -> str: 56 | """ 57 | Get the placeholder from the uri_config_option 58 | """ 59 | try: 60 | # i.e. issues_pr_uri -> pr 61 | return uri_config_option[:-4].split("_", maxsplit=1)[1] 62 | except IndexError: 63 | # issues_uri -> issue 64 | return uri_config_option[:-5] 65 | 66 | 67 | def _get_uri_template( 68 | config: Config, 69 | uri_config_option: str, 70 | ) -> str: 71 | """ 72 | Get a URL format template that can be filled with user information based 73 | on the given configuration 74 | 75 | The result always contains the following placeholder 76 | - n (the issue number, user, pull request, etc...) 77 | 78 | The result can contain the following other placeholders 79 | - group (same as user in github) 80 | - project 81 | 82 | Examples for possible results: 83 | 84 | - "https://github.com/{group}/{project}/issues/{n}" 85 | 86 | - "https://gitlab.company.com/{group}/{project}/{n}" 87 | 88 | - "https://fancy.issuetrack.com?group={group}&project={project}&issue={n}" 89 | 90 | Raises: 91 | - ValueError if the given uri contains an invalid placeholder 92 | """ 93 | format_string = str(getattr(config, uri_config_option)) 94 | placeholder = _get_placeholder(uri_config_option) 95 | 96 | result = format_string.replace(f"{{{placeholder}}}", "{n}") 97 | 98 | try: 99 | result.format(project="", group="", n="") 100 | except (NameError, KeyError) as e: 101 | raise ValueError( 102 | f"The `{uri_config_option}` option contains invalid placeholders. " 103 | f"Only {{group}}, {{projects}} and {{{placeholder}}} are allowed." 104 | f'Invalid format string: "{format_string}".' 105 | ) from e 106 | return result 107 | 108 | 109 | def _get_uri( 110 | uri_config_option: str, 111 | config: Config, 112 | number: str, 113 | group_and_project: Optional[tuple[str, str]] = None, 114 | ) -> str: 115 | """ 116 | Get a URI based on the given configuration and do some sanity checking 117 | """ 118 | format_string = _get_uri_template(config, uri_config_option) 119 | 120 | url_vars = {"n": number} 121 | 122 | config_group_and_project = _get_default_group_and_project(config, uri_config_option) 123 | if group_and_project: 124 | # Group and Project defined by call 125 | if config_group_and_project: 126 | to_replace = "/".join(config_group_and_project) 127 | if to_replace in format_string: 128 | # Backward compatibility, replace default group/project 129 | # with {group}/{project} 130 | format_string = format_string.replace(to_replace, "{group}/{project}") 131 | (url_vars["group"], url_vars["project"]) = group_and_project 132 | elif config_group_and_project: 133 | # If not defined by call use the default if given 134 | (url_vars["group"], url_vars["project"]) = config_group_and_project 135 | 136 | try: 137 | return format_string.format(**url_vars) 138 | except (NameError, KeyError) as e: 139 | # The format string was checked before, that it contains no additional not 140 | # supported placeholders. So this occur 141 | raise ValueError( 142 | f"The `{uri_config_option}` format `{format_string}` requires a " 143 | f"group/project to be defined in `issues_default_group_project`." 144 | ) from e 145 | 146 | 147 | def pypi_role(name, rawtext, text, lineno, inliner, options=None, content=None): 148 | """Sphinx role for linking to a PyPI on https://pypi.org/. 149 | 150 | Examples: :: 151 | 152 | :pypi:`sphinx-issues` 153 | 154 | """ 155 | options = options or {} 156 | content = content or [] 157 | has_explicit_title, title, target = split_explicit_title(text) 158 | 159 | target = utils.unescape(target).strip() 160 | title = utils.unescape(title).strip() 161 | ref = f"https://pypi.org/project/{target}" 162 | text = title if has_explicit_title else target 163 | link = nodes.reference(text=text, refuri=ref, **options) 164 | return [link], [] 165 | 166 | 167 | class IssueRole: 168 | # Symbols used to separate and issue/pull request/merge request etc 169 | # i.e 170 | # - group/project#2323 for issues 171 | # - group/project!1234 for merge requests (in gitlab) 172 | # - group/project@adbc1234 for commits 173 | ELEMENT_SEPARATORS = "#@!" 174 | 175 | EXTERNAL_REPO_REGEX = re.compile(rf"^(.+)/(.+)([{ELEMENT_SEPARATORS}])(\w+)$") 176 | 177 | def __init__( 178 | self, 179 | config_prefix: str, 180 | pre_format_text: Callable[[Config, str], str] = None, 181 | ): 182 | self.uri_config = f"{config_prefix}_uri" 183 | self.separator_config = f"{config_prefix}_prefix" 184 | self.pre_format_text = pre_format_text or self.default_pre_format_text 185 | 186 | @staticmethod 187 | def default_pre_format_text(config: Config, text: str) -> str: 188 | return text 189 | 190 | def format_text(self, config: Config, issue_no: str) -> str: 191 | """ 192 | Add supported separator in front of the issue or raise an error if invalid 193 | separator is given 194 | """ 195 | separator = getattr(config, self.separator_config) 196 | if separator not in self.ELEMENT_SEPARATORS: 197 | raise ValueError( 198 | f"Option {self.separator_config} has to be one of " 199 | f"{', '.join(self.ELEMENT_SEPARATORS)}." 200 | ) 201 | text = self.pre_format_text(config, issue_no.lstrip(self.ELEMENT_SEPARATORS)) 202 | return f"{separator}{text}" 203 | 204 | def make_node(self, name: str, issue_no: str, config: Config, options=None): 205 | if issue_no in ("-", "0"): 206 | return None 207 | 208 | options = options or {} 209 | 210 | has_explicit_title, title, target = split_explicit_title(issue_no) 211 | 212 | if has_explicit_title: 213 | issue_no = str(target) 214 | 215 | repo_match = self.EXTERNAL_REPO_REGEX.match(issue_no) 216 | 217 | if repo_match: 218 | # External repo 219 | group, project, original_separator, issue_no = repo_match.groups() 220 | text = f"{group}/{project}{self.format_text(config, issue_no)}" 221 | ref = _get_uri( 222 | self.uri_config, 223 | config, 224 | issue_no, 225 | (group, project), 226 | ) 227 | else: 228 | text = self.format_text(config, issue_no) 229 | ref = _get_uri(self.uri_config, config, issue_no) 230 | if has_explicit_title: 231 | return nodes.reference(text=title, refuri=ref, **options) 232 | else: 233 | return nodes.reference(text=text, refuri=ref, **options) 234 | 235 | def __call__( 236 | self, name, rawtext, text, lineno, inliner, options=None, content=None 237 | ): 238 | options = options or {} 239 | content = content or [] 240 | issue_nos = [each.strip() for each in utils.unescape(text).split(",")] 241 | config = inliner.document.settings.env.app.config 242 | ret = [] 243 | for i, issue_no in enumerate(issue_nos): 244 | node = self.make_node(name, issue_no, config, options=options) 245 | ret.append(node) 246 | if i != len(issue_nos) - 1: 247 | sep = nodes.raw(text=", ", format="html") 248 | ret.append(sep) 249 | return ret, [] 250 | 251 | 252 | """Sphinx role for linking to an issue. Must have 253 | `issues_uri` or `issues_default_group_project` configured in ``conf.py``. 254 | Examples: :: 255 | :issue:`123` 256 | :issue:`42,45` 257 | :issue:`sloria/konch#123` 258 | """ 259 | issue_role = IssueRole( 260 | config_prefix="issues", 261 | ) 262 | 263 | """Sphinx role for linking to a pull request. Must have 264 | `issues_pr_uri` or `issues_default_group_project` configured in ``conf.py``. 265 | Examples: :: 266 | :pr:`123` 267 | :pr:`42,45` 268 | :pr:`sloria/konch#43` 269 | """ 270 | pr_role = IssueRole( 271 | config_prefix="issues_pr", 272 | ) 273 | 274 | 275 | def format_commit_text(config, sha): 276 | return sha[:7] 277 | 278 | 279 | """Sphinx role for linking to a commit. Must have 280 | `issues_commit_uri` or `issues_default_group_project` configured in ``conf.py``. 281 | Examples: :: 282 | :commit:`123abc456def` 283 | :commit:`sloria/konch@123abc456def` 284 | """ 285 | commit_role = IssueRole( 286 | config_prefix="issues_commit", 287 | pre_format_text=format_commit_text, 288 | ) 289 | 290 | """Sphinx role for linking to a user profile. Defaults to linking to 291 | GitHub profiles, but the profile URIS can be configured via the 292 | ``issues_user_uri`` config value. 293 | 294 | Examples: :: 295 | 296 | :user:`sloria` 297 | 298 | Anchor text also works: :: 299 | 300 | :user:`Steven Loria ` 301 | """ 302 | user_role = IssueRole(config_prefix="issues_user") 303 | 304 | 305 | def setup(app): 306 | # Format template for issues URI 307 | # e.g. 'https://github.com/sloria/marshmallow/issues/{issue} 308 | app.add_config_value( 309 | "issues_uri", 310 | default="https://github.com/{group}/{project}/issues/{issue}", 311 | rebuild="html", 312 | types=[str], 313 | ) 314 | app.add_config_value("issues_prefix", default="#", rebuild="html", types=[str]) 315 | # Format template for PR URI 316 | # e.g. 'https://github.com/sloria/marshmallow/pull/{issue} 317 | app.add_config_value( 318 | "issues_pr_uri", 319 | default="https://github.com/{group}/{project}/pull/{pr}", 320 | rebuild="html", 321 | types=[str], 322 | ) 323 | app.add_config_value("issues_pr_prefix", default="#", rebuild="html", types=[str]) 324 | # Format template for commit URI 325 | # e.g. 'https://github.com/sloria/marshmallow/commits/{commit} 326 | app.add_config_value( 327 | "issues_commit_uri", 328 | default="https://github.com/{group}/{project}/commit/{commit}", 329 | rebuild="html", 330 | types=[str], 331 | ) 332 | app.add_config_value( 333 | "issues_commit_prefix", default="@", rebuild="html", types=[str] 334 | ) 335 | # There is no seperator config as a format_text function is given 336 | 337 | # Default User (Group)/Project eg. 'sloria/marshmallow' 338 | # Called github as the package was working with github only before 339 | app.add_config_value( 340 | "issues_github_path", default=None, rebuild="html", types=[str] 341 | ) 342 | # Same as above but with new naming to reflect the new functionality 343 | # Only on of both can be set 344 | app.add_config_value( 345 | "issues_default_group_project", default=None, rebuild="html", types=[str] 346 | ) 347 | # Format template for user profile URI 348 | # e.g. 'https://github.com/{user}' 349 | app.add_config_value( 350 | "issues_user_uri", 351 | # Default to sponsors URL. 352 | # GitHub will automatically redirect to profile 353 | # if Sponsors isn't set up. 354 | default="https://github.com/sponsors/{user}", 355 | rebuild="html", 356 | types=[str], 357 | ) 358 | app.add_config_value("issues_user_prefix", default="@", rebuild="html", types=[str]) 359 | app.add_role("issue", issue_role) 360 | app.add_role("pr", pr_role) 361 | app.add_role("user", user_role) 362 | app.add_role("commit", commit_role) 363 | app.add_role("pypi", pypi_role) 364 | return { 365 | "version": importlib.metadata.version("sphinx-issues"), 366 | "parallel_read_safe": True, 367 | "parallel_write_safe": True, 368 | } 369 | --------------------------------------------------------------------------------