pax_global_header00006660000000000000000000000064147023565310014520gustar00rootroot0000000000000052 comment=b2120ce91071403c2422b768c93020b57342def9 python-sphinx-issues-5.0.0/000077500000000000000000000000001470235653100156635ustar00rootroot00000000000000python-sphinx-issues-5.0.0/.github/000077500000000000000000000000001470235653100172235ustar00rootroot00000000000000python-sphinx-issues-5.0.0/.github/dependabot.yml000066400000000000000000000003301470235653100220470ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" python-sphinx-issues-5.0.0/.github/workflows/000077500000000000000000000000001470235653100212605ustar00rootroot00000000000000python-sphinx-issues-5.0.0/.github/workflows/build-release.yml000066400000000000000000000043401470235653100245210ustar00rootroot00000000000000name: build on: push: branches: ["main"] tags: ["*"] pull_request: jobs: tests: name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - { name: "3.9", python: "3.9", tox: py39 } - { name: "3.13", python: "3.13", tox: py313 } steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} allow-prereleases: true - run: python -m pip install --upgrade pip - run: python -m pip install tox - run: python -m tox -e ${{ matrix.tox }} build: name: Build package runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install pypa/build run: python -m pip install build - name: Build a binary wheel and a source tarball run: python -m build - name: Install twine run: python -m pip install twine - name: Check build run: python -m twine check --strict dist/* - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ # this duplicates pre-commit.ci, so only run it on tags # it guarantees that linting is passing prior to a release lint-pre-release: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: "3.13" - run: python -m pip install tox - run: python -m tox -e lint publish-to-pypi: name: PyPI release if: startsWith(github.ref, 'refs/tags/') needs: [build, tests, lint-pre-release] runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/sphinx-issues permissions: id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 python-sphinx-issues-5.0.0/.gitignore000066400000000000000000000006521470235653100176560ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Complexity output/*.html output/*/index.html # Sphinx docs/_build README.html # mypy .mypy_cache/ # ruff .ruff_cache python-sphinx-issues-5.0.0/.pre-commit-config.yaml000066400000000000000000000006701470235653100221470ustar00rootroot00000000000000ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.3 hooks: - id: ruff - id: ruff-format - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.29.2 hooks: - id: check-github-workflows - id: check-readthedocs - repo: https://github.com/asottile/blacken-docs rev: 1.18.0 hooks: - id: blacken-docs additional_dependencies: [black==23.12.1] python-sphinx-issues-5.0.0/CONTRIBUTING.md000066400000000000000000000005761470235653100201240ustar00rootroot00000000000000# Contributing ## Setting up for development - Create and activate a new virtual environment - `pip install -e '.[dev]'` - (Optional but recommended) Install the pre-commit hooks, which will format and lint your git staged files: ``` # The pre-commit CLI was installed above pre-commit install ``` - To run tests: ``` pytest ``` - To run syntax checks: ``` tox -e lint ``` python-sphinx-issues-5.0.0/LICENSE000066400000000000000000000020501470235653100166650ustar00rootroot00000000000000Copyright Steven Loria and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-sphinx-issues-5.0.0/NOTICE000066400000000000000000000026421470235653100165730ustar00rootroot00000000000000sphinx-issues includes code adapted from other libraries. Their licenses are included here. releases License ================ Copyright (c) 2014, Jeff Forcier All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-sphinx-issues-5.0.0/README.rst000066400000000000000000000156451470235653100173650ustar00rootroot00000000000000============= sphinx-issues ============= .. image:: https://badgen.net/pypi/v/sphinx-issues :target: https://pypi.org/project/sphinx-issues/ :alt: PyPI badge .. image:: https://github.com/sloria/sphinx-issues/actions/workflows/build-release.yml/badge.svg :target: https://github.com/sloria/sphinx-issues/actions/workflows/build-release.yml :alt: Build status 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). Example ******* For an example usage, check out `marshmallow's changelog `_, which makes use of the roles in this library. Installation and Configuration ****************************** .. code-block:: console pip install sphinx-issues Add ``sphinx_issues`` to ``extensions`` in your ``conf.py``. The extension has default values for GitHub projects. Simply set the add the ``issues_default_group_project`` config variable and you are good to go: .. code-block:: python # docs/conf.py # ... extensions = [ # ... "sphinx_issues" ] # Path to GitHub repo {group}/{project} (note that `group` is the GitHub user or organization) issues_github_path = "sloria/marshmallow" # which is the equivalent to: issues_uri = "https://github.com/{group}/{project}/issues/{issue}" issues_prefix = "#" issues_pr_uri = "https://github.com/{group}/{project}/pull/{pr}" issues_pr_prefix = "#" issues_commit_uri = "https://github.com/{group}/{project}/commit/{commit}" issues_commit_prefix = "@" issues_user_uri = "https://github.com/{user}" issues_user_prefix = "@" The extension is very configurable and can be used with any kind of issue tracker. Here is how you could configure it for use with a custom hosed GitLab instance: .. code-block:: python # docs/conf.py # ... extensions = [ # ... "sphinx_issues" ] # Default repo {group}/{project} of gitlab project issues_default_group_project = "myteam/super_great_project" issues_uri = "https://gitlab.company.com/{group}/{project}/-/issues/{issue}" issues_prefix = "#" issues_pr_uri = "https://gitlab.company.com/{group}/{project}/-/merge_requests/{pr}" issues_pr_prefix = "!" issues_commit_uri = "https://gitlab.company.com/{group}/{project}/-/commit/{commit}" issues_commit_prefix = "@" issues_user_uri = "https://gitlab.company.com/{user}" issues_user_prefix = "@" Usage inside the documentation ****************************** Use the ``:issue:`` and ``:pr:`` roles in your docs like so: .. code-block:: rst See issue :issue:`42` See issues :issue:`12,13` See :issue:`sloria/konch#45`. See PR :pr:`58` Use the ``:user:`` role in your docs to link to user profiles (GitHub by default, but can be configured via the ``issues_user_uri`` config variable). Use the ``:commit:`` role to link to commits. .. code-block:: rst Fixed in :commit:`6bb9124d5e9dbb2f7b52864c3d8af7feb1b69403`. .. code-block:: rst Thanks to :user:`bitprophet` for the idea! You can also use explicit names if you want to use a different name than the github user name: .. code-block:: rst This change is due to :user:`Andreas Mueller `. The syntax ``:role:`My custom title ``` works for all roles of this extension. It can be also used in combination with a list: .. code-block:: rst Fix bad bug :issue:`123, (Duplicate) <199>` Use the ``:pypi:`` role to link to PyPI on https://pypi.org. .. code-block:: rst :pypi:`sphinx-issues` - A Sphinx extension for linking to your project's issue tracker. Credits ******* 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. License ******* MIT licensed. See the bundled `LICENSE `_ file for more details. Changelog ********* 5.0.0 (2024-10-11) ------------------ - Remove `:cwe:` and `:cve:` roles, as these are officially included in Sphinx>=8.1.0. - Support Python 3.9-3.13. Python 3.8 is no longer supported. 4.1.0 (2024-04-14) ------------------ - Add `:pypi:` role for linking to PyPI projects (`#144 `_). Thanks @shenxianpeng for the suggestion and PR. 4.0.0 (2024-01-19) ------------------ - Default to linking GH Sponsors for the :user: role (`#129 `_). Thanks @webknjaz for the suggestion. - Support Python 3.8-3.12. Older versions are no longer supported. - *Backwards-incompatible*: Remove ``__version__``, ``__author__``, and ``__license__`` attributes. Use ``importlib.metadata`` to read this metadata instead. 3.0.1 (2022-01-11) ------------------ - Fix regression from 3.0.0: `exception: 'in ' requires string as left operand, not type`. 3.0.0 (2022-01-10) ------------------ - The `:commit:` role now outputs with an `@` prefix. - Add configuration options for changing prefixes. - Allow `{group}` to be specified within `issues_uri`, `issues_pr_uri`, `issues_commit_uri`, and 2.0.0 (2022-01-01) ------------------ - Drop support for Python 2.7 and 3.5. - Test against Python 3.8 to 3.10. - Add ``:cwe:`` role for linking to CVEs on https://cwe.mitre.org. Thanks @hugovk for the PR. - Add support for custom urls and separators `Issue #93 `_ - Allow custom titles for all roles `Issue #116 `_ - Added setting `issues_default_group_project` as future replacement of `issues_github_path`, to reflect the now to universal nature of the extension 1.2.0 (2018-12-26) ------------------ - Add ``:commit:`` role for linking to commits. - Add support for linking to external repos. - Test against Python 3.7. 1.1.0 (2018-09-18) ------------------ - Add ``:cve:`` role for linking to CVEs on https://cve.mitre.org. 1.0.0 (2018-07-14) ------------------ - Add ``:pr:`` role. Thanks @jnotham for the suggestion. - Drop support for Python 3.4. 0.4.0 (2017-11-25) ------------------ - Raise ``ValueError`` if neither ``issues_uri`` nor ``issues_github_path`` is set. Thanks @jnothman for the PR. - Drop support for Python 2.6 and 3.3. 0.3.1 (2017-01-16) ------------------ - ``setup`` returns metadata, preventing warnings about parallel reads and writes. Thanks @jfinkels for reporting. 0.3.0 (2016-10-20) ------------------ - Support anchor text for ``:user:`` role. Thanks @jnothman for the suggestion and thanks @amueller for the PR. 0.2.0 (2014-12-22) ------------------ - Add ``:user:`` role for linking to GitHub user profiles. 0.1.0 (2014-12-21) ------------------ - Initial release. python-sphinx-issues-5.0.0/RELEASING.md000066400000000000000000000004241470235653100175160ustar00rootroot00000000000000# Releasing 1. Bump version in `pyproject.toml` and update the changelog with today's date. 2. Commit: `git commit -m "Bump version and update changelog"` 3. Tag the commit: `git tag x.y.z` 4. Push: `git push --tags origin main`. CI will take care of the PyPI release. python-sphinx-issues-5.0.0/pyproject.toml000066400000000000000000000032251470235653100206010ustar00rootroot00000000000000[project] name = "sphinx-issues" version = "5.0.0" description = "A Sphinx extension for linking to your project's issue tracker" readme = "README.rst" license = { file = "LICENSE" } authors = [{ name = "Steven Loria", email = "sloria1@gmail.com" }] classifiers = [ "Framework :: Sphinx :: Extension", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Documentation", ] keywords = ["sphinx", "issues", "github"] requires-python = ">=3.9" dependencies = ["sphinx"] [project.urls] Issues = "https://github.com/sloria/sphinx-issues/issues" Source = "https://github.com/sloria/sphinx-issues" [project.optional-dependencies] tests = ["pytest"] dev = ["sphinx-issues[tests]", "tox", "pre-commit>=3.6,<5.0"] [build-system] requires = ["flit_core<4"] build-backend = "flit_core.buildapi" [tool.flit.sdist] include = ["tests/", "CONTRIBUTING.md", "NOTICE", "tox.ini"] exclude = ["docs/_build/"] [tool.ruff] src = ["src"] fix = true show-fixes = true output-format = "full" [tool.ruff.format] docstring-code-format = true [tool.ruff.lint] ignore = ["E203", "E266", "E501", "E731"] select = [ "B", # flake8-bugbear "E", # pycodestyle error "F", # pyflakes "I", # isort "UP", # pyupgrade "W", # pycodestyle warning ] [tool.pytest.ini_options] filterwarnings = [ "ignore:is already registered, its visitors will be overridden:UserWarning", ] python-sphinx-issues-5.0.0/src/000077500000000000000000000000001470235653100164525ustar00rootroot00000000000000python-sphinx-issues-5.0.0/src/sphinx_issues/000077500000000000000000000000001470235653100213565ustar00rootroot00000000000000python-sphinx-issues-5.0.0/src/sphinx_issues/__init__.py000066400000000000000000000277561470235653100235100ustar00rootroot00000000000000"""A Sphinx extension for linking to your project's issue tracker.""" import importlib.metadata import re from typing import Callable, Optional from docutils import nodes, utils from sphinx.config import Config from sphinx.util.nodes import split_explicit_title GITHUB_USER_RE = re.compile("^https://github.com/([^/]+)/([^/]+)/.*") def _get_default_group_and_project( config: Config, uri_config_option: str ) -> Optional[tuple[str, str]]: """ Get the default group/project or None if not set """ old_config = getattr(config, "issues_github_path", None) new_config = getattr(config, "issues_default_group_project", None) if old_config and new_config: raise ValueError( "Both 'issues_github_path' and 'issues_default_group_project' are set, even" " though they define the same setting. " "Please only define one of these." ) group_and_project = new_config or old_config if group_and_project: assert isinstance(group_and_project, str) try: group, project = group_and_project.split("/", maxsplit=1) return group, project except ValueError as e: raise ValueError( "`issues_github_path` or `issues_default_group_project` needs to " "define a value in the form of `/` " f"but `{config}` was given." ) from e # If group and project was not set, we need to look for it within the github url # for backward compatibility if not group_and_project: uri = getattr(config, uri_config_option) if uri: match = GITHUB_USER_RE.match(uri) if match: return match.groups()[0], match.groups()[1] return None def _get_placeholder(uri_config_option: str) -> str: """ Get the placeholder from the uri_config_option """ try: # i.e. issues_pr_uri -> pr return uri_config_option[:-4].split("_", maxsplit=1)[1] except IndexError: # issues_uri -> issue return uri_config_option[:-5] def _get_uri_template( config: Config, uri_config_option: str, ) -> str: """ Get a URL format template that can be filled with user information based on the given configuration The result always contains the following placeholder - n (the issue number, user, pull request, etc...) The result can contain the following other placeholders - group (same as user in github) - project Examples for possible results: - "https://github.com/{group}/{project}/issues/{n}" - "https://gitlab.company.com/{group}/{project}/{n}" - "https://fancy.issuetrack.com?group={group}&project={project}&issue={n}" Raises: - ValueError if the given uri contains an invalid placeholder """ format_string = str(getattr(config, uri_config_option)) placeholder = _get_placeholder(uri_config_option) result = format_string.replace(f"{{{placeholder}}}", "{n}") try: result.format(project="", group="", n="") except (NameError, KeyError) as e: raise ValueError( f"The `{uri_config_option}` option contains invalid placeholders. " f"Only {{group}}, {{projects}} and {{{placeholder}}} are allowed." f'Invalid format string: "{format_string}".' ) from e return result def _get_uri( uri_config_option: str, config: Config, number: str, group_and_project: Optional[tuple[str, str]] = None, ) -> str: """ Get a URI based on the given configuration and do some sanity checking """ format_string = _get_uri_template(config, uri_config_option) url_vars = {"n": number} config_group_and_project = _get_default_group_and_project(config, uri_config_option) if group_and_project: # Group and Project defined by call if config_group_and_project: to_replace = "/".join(config_group_and_project) if to_replace in format_string: # Backward compatibility, replace default group/project # with {group}/{project} format_string = format_string.replace(to_replace, "{group}/{project}") (url_vars["group"], url_vars["project"]) = group_and_project elif config_group_and_project: # If not defined by call use the default if given (url_vars["group"], url_vars["project"]) = config_group_and_project try: return format_string.format(**url_vars) except (NameError, KeyError) as e: # The format string was checked before, that it contains no additional not # supported placeholders. So this occur raise ValueError( f"The `{uri_config_option}` format `{format_string}` requires a " f"group/project to be defined in `issues_default_group_project`." ) from e def pypi_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Sphinx role for linking to a PyPI on https://pypi.org/. Examples: :: :pypi:`sphinx-issues` """ options = options or {} content = content or [] has_explicit_title, title, target = split_explicit_title(text) target = utils.unescape(target).strip() title = utils.unescape(title).strip() ref = f"https://pypi.org/project/{target}" text = title if has_explicit_title else target link = nodes.reference(text=text, refuri=ref, **options) return [link], [] class IssueRole: # Symbols used to separate and issue/pull request/merge request etc # i.e # - group/project#2323 for issues # - group/project!1234 for merge requests (in gitlab) # - group/project@adbc1234 for commits ELEMENT_SEPARATORS = "#@!" EXTERNAL_REPO_REGEX = re.compile(rf"^(\w+)/(.+)([{ELEMENT_SEPARATORS}])([\w]+)$") def __init__( self, config_prefix: str, pre_format_text: Callable[[Config, str], str] = None, ): self.uri_config = f"{config_prefix}_uri" self.separator_config = f"{config_prefix}_prefix" self.pre_format_text = pre_format_text or self.default_pre_format_text @staticmethod def default_pre_format_text(config: Config, text: str) -> str: return text def format_text(self, config: Config, issue_no: str) -> str: """ Add supported separator in front of the issue or raise an error if invalid separator is given """ separator = getattr(config, self.separator_config) if separator not in self.ELEMENT_SEPARATORS: raise ValueError( f"Option {self.separator_config} has to be one of " f"{', '.join(self.ELEMENT_SEPARATORS)}." ) text = self.pre_format_text(config, issue_no.lstrip(self.ELEMENT_SEPARATORS)) return f"{separator}{text}" def make_node(self, name: str, issue_no: str, config: Config, options=None): if issue_no in ("-", "0"): return None options = options or {} has_explicit_title, title, target = split_explicit_title(issue_no) if has_explicit_title: issue_no = str(target) repo_match = self.EXTERNAL_REPO_REGEX.match(issue_no) if repo_match: # External repo group, project, original_separator, issue_no = repo_match.groups() text = f"{group}/{project}{self.format_text(config, issue_no)}" ref = _get_uri( self.uri_config, config, issue_no, (group, project), ) else: text = self.format_text(config, issue_no) ref = _get_uri(self.uri_config, config, issue_no) if has_explicit_title: return nodes.reference(text=title, refuri=ref, **options) else: return nodes.reference(text=text, refuri=ref, **options) def __call__( self, name, rawtext, text, lineno, inliner, options=None, content=None ): options = options or {} content = content or [] issue_nos = [each.strip() for each in utils.unescape(text).split(",")] config = inliner.document.settings.env.app.config ret = [] for i, issue_no in enumerate(issue_nos): node = self.make_node(name, issue_no, config, options=options) ret.append(node) if i != len(issue_nos) - 1: sep = nodes.raw(text=", ", format="html") ret.append(sep) return ret, [] """Sphinx role for linking to an issue. Must have `issues_uri` or `issues_default_group_project` configured in ``conf.py``. Examples: :: :issue:`123` :issue:`42,45` :issue:`sloria/konch#123` """ issue_role = IssueRole( config_prefix="issues", ) """Sphinx role for linking to a pull request. Must have `issues_pr_uri` or `issues_default_group_project` configured in ``conf.py``. Examples: :: :pr:`123` :pr:`42,45` :pr:`sloria/konch#43` """ pr_role = IssueRole( config_prefix="issues_pr", ) def format_commit_text(config, sha): return sha[:7] """Sphinx role for linking to a commit. Must have `issues_commit_uri` or `issues_default_group_project` configured in ``conf.py``. Examples: :: :commit:`123abc456def` :commit:`sloria/konch@123abc456def` """ commit_role = IssueRole( config_prefix="issues_commit", pre_format_text=format_commit_text, ) """Sphinx role for linking to a user profile. Defaults to linking to GitHub profiles, but the profile URIS can be configured via the ``issues_user_uri`` config value. Examples: :: :user:`sloria` Anchor text also works: :: :user:`Steven Loria ` """ user_role = IssueRole(config_prefix="issues_user") def setup(app): # Format template for issues URI # e.g. 'https://github.com/sloria/marshmallow/issues/{issue} app.add_config_value( "issues_uri", default="https://github.com/{group}/{project}/issues/{issue}", rebuild="html", types=[str], ) app.add_config_value("issues_prefix", default="#", rebuild="html", types=[str]) # Format template for PR URI # e.g. 'https://github.com/sloria/marshmallow/pull/{issue} app.add_config_value( "issues_pr_uri", default="https://github.com/{group}/{project}/pull/{pr}", rebuild="html", types=[str], ) app.add_config_value("issues_pr_prefix", default="#", rebuild="html", types=[str]) # Format template for commit URI # e.g. 'https://github.com/sloria/marshmallow/commits/{commit} app.add_config_value( "issues_commit_uri", default="https://github.com/{group}/{project}/commit/{commit}", rebuild="html", types=[str], ) app.add_config_value( "issues_commit_prefix", default="@", rebuild="html", types=[str] ) # There is no seperator config as a format_text function is given # Default User (Group)/Project eg. 'sloria/marshmallow' # Called github as the package was working with github only before app.add_config_value( "issues_github_path", default=None, rebuild="html", types=[str] ) # Same as above but with new naming to reflect the new functionality # Only on of both can be set app.add_config_value( "issues_default_group_project", default=None, rebuild="html", types=[str] ) # Format template for user profile URI # e.g. 'https://github.com/{user}' app.add_config_value( "issues_user_uri", # Default to sponsors URL. # GitHub will automatically redirect to profile # if Sponsors isn't set up. default="https://github.com/sponsors/{user}", rebuild="html", types=[str], ) app.add_config_value("issues_user_prefix", default="@", rebuild="html", types=[str]) app.add_role("issue", issue_role) app.add_role("pr", pr_role) app.add_role("user", user_role) app.add_role("commit", commit_role) app.add_role("pypi", pypi_role) return { "version": importlib.metadata.version("sphinx-issues"), "parallel_read_safe": True, "parallel_write_safe": True, } python-sphinx-issues-5.0.0/tests/000077500000000000000000000000001470235653100170255ustar00rootroot00000000000000python-sphinx-issues-5.0.0/tests/source/000077500000000000000000000000001470235653100203255ustar00rootroot00000000000000python-sphinx-issues-5.0.0/tests/source/_static/000077500000000000000000000000001470235653100217535ustar00rootroot00000000000000python-sphinx-issues-5.0.0/tests/source/_static/.gitkeep000066400000000000000000000000001470235653100233720ustar00rootroot00000000000000python-sphinx-issues-5.0.0/tests/source/conf.py000066400000000000000000000044301470235653100216250ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = "sphinx-issues" copyright = "2022, foobar" author = "foobar" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx_issues"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # suppress_warnings = ["app.add_node"] issues_uri = "https://gitlab.company.com/{group}/{project}/-/issues/{issue}" issues_prefix = "#" issues_pr_uri = "https://gitlab.company.com/{group}/{project}/-/merge_requests/{pr}" issues_pr_prefix = "!" issues_commit_uri = "https://gitlab.company.com/{group}/{project}/-/commit/{commit}" issues_commit_prefix = "@" issues_user_uri = "https://gitlab.company.com/{user}" issues_user_prefix = "@" issues_default_group_project = "myteam/super_great_project" python-sphinx-issues-5.0.0/tests/source/examples.rst000066400000000000000000000002151470235653100226730ustar00rootroot00000000000000Examples: - See issues :issue:`12,13` - See other issues :issue:`sloria/konch#45,46`. - See PR :pr:`58`, thanks :user:`kound` python-sphinx-issues-5.0.0/tests/source/index.rst000066400000000000000000000002141470235653100221630ustar00rootroot00000000000000 Welcome to sphinx-issues's documentation! ========================================= .. include:: examples.rst .. include:: ../README.rst python-sphinx-issues-5.0.0/tests/test_sphinx_issues.py000066400000000000000000000260751470235653100233540ustar00rootroot00000000000000import subprocess import sys from pathlib import Path from shutil import rmtree from tempfile import mkdtemp from unittest.mock import Mock import pytest import sphinx.application from sphinx_issues import ( commit_role, issue_role, pr_role, pypi_role, user_role, ) from sphinx_issues import setup as issues_setup BASE_DIR = Path(__file__).parent.absolute() @pytest.fixture( params=[ # Parametrize config {"issues_github_path": "marshmallow-code/marshmallow"}, {"issues_default_group_project": "marshmallow-code/marshmallow"}, { "issues_uri": "https://github.com/marshmallow-code/marshmallow/issues/{issue}", "issues_pr_uri": "https://github.com/marshmallow-code/marshmallow/pull/{pr}", "issues_commit_uri": "https://github.com/marshmallow-code/marshmallow/commit/{commit}", }, ] ) def app(request): src, doctree, confdir, outdir = (mkdtemp() for _ in range(4)) sphinx.application.Sphinx._log = lambda self, message, wfile, nonl=False: None app = sphinx.application.Sphinx( srcdir=src, confdir=None, outdir=outdir, doctreedir=doctree, buildername="html" ) issues_setup(app) # Stitch together as the sphinx app init() usually does w/ real conf files app.config._raw_config = request.param try: app.config.init_values() except TypeError: app.config.init_values(lambda x: x) yield app [rmtree(x) for x in (src, doctree, confdir, outdir)] @pytest.fixture() def inliner(app): return Mock(document=Mock(settings=Mock(env=Mock(app=app)))) @pytest.mark.parametrize( ("role", "role_name", "text", "expected_text", "expected_url"), [ ( issue_role, "issue", "42", "#42", "https://github.com/marshmallow-code/marshmallow/issues/42", ), ( issue_role, "issue", "Hard Issue <42>", "Hard Issue", "https://github.com/marshmallow-code/marshmallow/issues/42", ), ( issue_role, "issue", "Not my business ", "Not my business", "https://github.com/foo/bar/issues/42", ), ( pr_role, "pr", "42", "#42", "https://github.com/marshmallow-code/marshmallow/pull/42", ), (user_role, "user", "sloria", "@sloria", "https://github.com/sponsors/sloria"), ( user_role, "user", "Steven Loria ", "Steven Loria", "https://github.com/sponsors/sloria", ), ( pypi_role, "pypi", "sphinx-issues", "sphinx-issues", "https://pypi.org/project/sphinx-issues", ), ( commit_role, "commit", "123abc456def", "@123abc4", "https://github.com/marshmallow-code/marshmallow/commit/123abc456def", ), # External issue ( issue_role, "issue", "sloria/webargs#42", "sloria/webargs#42", "https://github.com/sloria/webargs/issues/42", ), # External PR ( pr_role, "pr", "sloria/webargs#42", "sloria/webargs#42", "https://github.com/sloria/webargs/pull/42", ), # External commit ( commit_role, "commit", "sloria/webargs@abc123def456", "sloria/webargs@abc123d", "https://github.com/sloria/webargs/commit/abc123def456", ), ], ) def test_roles(inliner, role, role_name, text, expected_text, expected_url): result = role(role_name, rawtext="", text=text, lineno=None, inliner=inliner) link = result[0][0] assert link.astext() == expected_text assert link.attributes["refuri"] == expected_url def test_issue_role_multiple(inliner): result = issue_role( name=None, rawtext="", text="a title <42>,43", inliner=inliner, lineno=None ) link1 = result[0][0] assert link1.astext() == "a title" issue_url = "https://github.com/marshmallow-code/marshmallow/issues/" assert link1.attributes["refuri"] == issue_url + "42" sep = result[0][1] assert sep.astext() == ", " link2 = result[0][2] assert link2.astext() == "#43" assert link2.attributes["refuri"] == issue_url + "43" def test_issue_role_multiple_with_external(inliner): result = issue_role( "issue", rawtext="", text="42,sloria/konch#43", inliner=inliner, lineno=None ) link1 = result[0][0] assert link1.astext() == "#42" issue_url = "https://github.com/marshmallow-code/marshmallow/issues/42" assert link1.attributes["refuri"] == issue_url sep = result[0][1] assert sep.astext() == ", " link2 = result[0][2] assert link2.astext() == "sloria/konch#43" assert link2.attributes["refuri"] == "https://github.com/sloria/konch/issues/43" @pytest.fixture def app_custom_uri(): src, doctree, confdir, outdir = (mkdtemp() for _ in range(4)) sphinx.application.Sphinx._log = lambda self, message, wfile, nonl=False: None app = sphinx.application.Sphinx( srcdir=src, confdir=None, outdir=outdir, doctreedir=doctree, buildername="html" ) issues_setup(app) # Stitch together as the sphinx app init() usually does w/ real conf files app.config._raw_config = { "issues_default_group_project": "myteam/super_great_project", "issues_uri": "https://gitlab.company.com/{group}/{project}/-/issues/{issue}", "issues_prefix": "#", "issues_pr_uri": "https://gitlab.company.com/{group}/{project}/-/merge_requests/{pr}", "issues_pr_prefix": "!", "issues_commit_uri": "https://gitlab.company.com/{group}/{project}/-/commit/{commit}", "issues_commit_prefix": "@", "issues_user_uri": "https://gitlab.company.com/{user}", "issues_user_prefix": "@", } try: app.config.init_values() except TypeError: app.config.init_values(lambda x: x) yield app [rmtree(x) for x in (src, doctree, confdir, outdir)] @pytest.fixture() def inliner_custom_uri(app_custom_uri): return Mock(document=Mock(settings=Mock(env=Mock(app=app_custom_uri)))) @pytest.mark.parametrize( ("role", "role_name", "text", "expected_text", "expected_url"), [ ( issue_role, "issue", "42", "#42", "https://gitlab.company.com/myteam/super_great_project/-/issues/42", ), ( issue_role, "issue", "Hard Issue <42>", "Hard Issue", "https://gitlab.company.com/myteam/super_great_project/-/issues/42", ), ( issue_role, "issue", "Not my business ", "Not my business", "https://gitlab.company.com/foo/bar/-/issues/42", ), ( pr_role, "pr", "42", "!42", "https://gitlab.company.com/myteam/super_great_project/-/merge_requests/42", ), (user_role, "user", "sloria", "@sloria", "https://gitlab.company.com/sloria"), ( user_role, "user", "Steven Loria ", "Steven Loria", "https://gitlab.company.com/sloria", ), ( commit_role, "commit", "123abc456def", "@123abc4", "https://gitlab.company.com/myteam/super_great_project/-/commit/123abc456def", ), # External issue ( issue_role, "issue", "sloria/webargs#42", "sloria/webargs#42", "https://gitlab.company.com/sloria/webargs/-/issues/42", ), # External PR ( pr_role, "pr", "sloria/webargs#42", "sloria/webargs!42", "https://gitlab.company.com/sloria/webargs/-/merge_requests/42", ), # External commit ( commit_role, "commit", "sloria/webargs@abc123def456", "sloria/webargs@abc123d", "https://gitlab.company.com/sloria/webargs/-/commit/abc123def456", ), ], ) def test_roles_custom_uri( inliner_custom_uri, role, role_name, text, expected_text, expected_url ): result = role( role_name, rawtext="", text=text, lineno=None, inliner=inliner_custom_uri ) link = result[0][0] assert link.astext() == expected_text assert link.attributes["refuri"] == expected_url @pytest.fixture def tmp_doc_build_folder(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: """Generate a temporary source folder and chdir in it. Return the build folder""" source = tmp_path / "source" build = tmp_path / "build" static = source / "_static" for folder in (source, build, static): folder.mkdir() conf_py = BASE_DIR / "source" / "conf.py" examples_rst = BASE_DIR / "source" / "examples.rst" source.joinpath("conf.py").write_bytes(conf_py.read_bytes()) source.joinpath("index.rst").write_bytes(examples_rst.read_bytes()) monkeypatch.chdir(source) return build def test_sphinx_build_integration(tmp_doc_build_folder: Path): """Ensure that a simulated complete sphinx run works as expected""" subprocess.run( [ Path(sys.executable).parent.joinpath("sphinx-build"), "-b", "html", "-W", # turn warnings into errors "-E", # force rebuild of environment (even if we work in tmp) ".", str(tmp_doc_build_folder), ], check=True, ) created = tmp_doc_build_folder / "index.html" assert created.exists() and created.is_file() content = created.read_text() issue_url = "https://gitlab.company.com/myteam/super_great_project/-/issues/" other_issue_url = "https://gitlab.company.com/sloria/konch/-/issues/" pr_url = "https://gitlab.company.com/myteam/super_great_project/-/merge_requests/" user_url = "https://gitlab.company.com/" # We could do something fancy like an HTML parser or regex: # Instead we keep it simple expected_strings = ( ( f"See issues " f'#12, ' f'#13' ), ( f"See other issues " f'sloria/konch#45,' f' #46' ), ( f'See PR !58, ' f'thanks @kound' ), ) # Ensure that we do no check character wise but line wise assert len(expected_strings) == 3 for expected in expected_strings: assert expected in content python-sphinx-issues-5.0.0/tox.ini000066400000000000000000000005251470235653100172000ustar00rootroot00000000000000[tox] envlist = lint,py{39,310,311,312,313} [testenv] extras = tests commands = pytest {posargs} [testenv:lint] deps = pre-commit~=3.6 skip_install = true commands = pre-commit run --all-files ; Below tasks are for development only (not run in CI) [testenv:watch-readme] deps = restview skip_install = true commands = restview README.rst