Compare commits

..

1 commit

Author SHA1 Message Date
Tiara Rodney
a5776f8e4b
dirty: init 2025-06-06 22:41:37 +02:00
26 changed files with 195 additions and 420 deletions

View file

@ -6,7 +6,7 @@ GPG_SIGNER_FINGERPRINT := "91CD826E74B0174D181903DEF97C70941CD8C4EF"
.PHONY: chore configure requirements-dev.txt requirements.txt publish archive .PHONY: chore configure requirements-dev.txt requirements.txt publish archive
chore: requirements.txt requirements-dev.txt bitbucket-pipelines.yaml chore: requirements.txt requirements-dev.txt bitbucket-pipelines.yml
Pipfile.lock: Pipfile Pipfile.lock: Pipfile
python3 -m pipenv lock -v python3 -m pipenv lock -v

View file

@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
byteb4rb1e.sphinxcontrib = { editable = true, path = '.'} byteb4rb1e_sphinxcontrib = { editable = true, path = '.'}
[dev-packages] [dev-packages]
sphinx = "*" sphinx = "*"

21
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "4eac08fb6ab8c543a1fa6030c3c69a57aa6ae6de69e4f47ec483128bb61b2867" "sha256": "48c822d9e7bedd5ee25106b31093ce10a0071d7b12f7861e4e4cc46c5549c9ed"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -32,9 +32,8 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==2.17.0" "version": "==2.17.0"
}, },
"byteb4rb1e.utils": { "byteb4rb1e.sphinxcontrib": {
"git": "https://bitbucket.org/byteb4rb1e/py-utils.git", "version": "==0.1.dev5+g3c348d5.d20250526"
"ref": "d0dfa1cb12702e6d25f3a9eeab02968eda8d06ba"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -382,11 +381,11 @@
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
"sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e",
"sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a" "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.9'",
"version": "==5.5.2" "version": "==6.0.0"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -833,11 +832,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", "sha256:49f7af965996f26d43c8ae34539c8d99c5042fbff34302ea151eaa9c207cd257",
"sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552" "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==80.7.1" "version": "==80.8.0"
}, },
"setuptools-scm": { "setuptools-scm": {
"hashes": [ "hashes": [

View file

@ -1,3 +1,21 @@
################################### ###################################
byteb4rb1e_sphinxcontrib.authorship byteb4rb1e_sphinxcontrib.authorship
################################### ###################################
An implementation of the IMJE recommendations on authorship applied to Sphinx
documents.
.. code-block:: rst
.. contribution:: Review
:name: Adam Smith
:organization: Example Corp.
:group: contributions
.. code-block:: rst
.. acknowledgment::
:name: Adam Smith
:organization: Example Corp.
:group: contributions

View file

@ -1,3 +1,3 @@
####################################### #######################################
byteb4rb1e_sphinxcontrib.svc_authorship byteb4rb1e_sphinxcontrib.authorship_svc
####################################### #######################################

View file

@ -12,7 +12,7 @@ via pip (PyPi)
.. code-block:: .. code-block::
$> python3 -m pip install byteb4rb1ie-sphinxcontrib $> python3 -m pip install byteb4rb1e-sphinxcontrib
via pip (Git) via pip (Git)
------------- -------------
@ -21,20 +21,6 @@ via pip (Git)
$> python3 -m pip git+https://bitbucket.org/byteb4rb1e/sphinxcontrib@master $> python3 -m pip git+https://bitbucket.org/byteb4rb1e/sphinxcontrib@master
via Git
-------
.. code-block::
$> git clone https://bitbucket.org/byteb4rb1e/sphinxcontrib
with pipenv
~~~~~~~~~~~
.. code-block::
$> sh ./configure --with-pipenv .pipenv
Usage Usage
===== =====
@ -42,5 +28,140 @@ Usage
:caption: Sphinx Extensions :caption: Sphinx Extensions
authorship authorship
svc_authorship authorship_svc
svc svc
Installation (Development)
==========================
.. code-block::
$> git clone https://bitbucket.org/byteb4rb1e/sphinxcontrib
.. warning::
(Non-MSYS2) Windows users MUST use ``.venv/Scripts/python3.exe``, instead of
``.venv/bin/python3``
.. warning::
(Non-MSYS2) Windows users MUST execute ``python3 -m pipenv install -d
--skip-lock``, instead of ``sh ./configure``.
with pipenv
-----------
``pipenv`` expected to be installed system-wide
.. code-block::
$> sh ./configure
with venv
---------
.. code-block::
$> python3 -m venv --system-site-packages .venv
$> .venv/bin/python3 -m pip install pipenv
$> .venv/bin/python3 -m pipenv run sh ./configure
with venv (but without configuration)
-------------------------------------
.. code-block::
$> python3 -m venv --system-site-packages .venv
$> .venv/bin/python3 -m pip install -r requirements-dev.txt
Development
===========
Static Code Analysis
--------------------
.. code-block::
$> python3 -m pipenv run test-static
Audit
~~~~~
.. code-block::
$> python3 -m tox -e audit
Format
~~~~~~
.. code-block::
$> python3 -m tox -e format -- --inline
Lint
~~~~
.. code-block::
$> python3 -m tox -e lint
Unit Testing
------------
.. code-block::
$> python3 -m pipenv run test-unit
Test Suite
~~~~~~~~~~
.. code-block::
$> python3 -m pipenv run test-unit -- -p tests/unit
Test Case
~~~~~~~~~
.. code-block::
$> python3 -m pipenv run test-unit -- -p tests/unit
Integration Testing
-------------------
.. code-block::
$> python3 -m pipenv run test-integration
Test Suite
~~~~~~~~~~
.. code-block::
$> python3 -m pipenv run test-integration -- \
-p tests/integration/byteb4rb1e_sphinxcontrib/authorship
Test Case
~~~~~~~~~
.. code-block::
$> python3 -m pipenv run test-integration -- \
-p tests/integration/byteb4rb1e_sphinxcontrib/authorship/test_setup.py
with a definitive Python and Sphinx major version (e.g. *Python* ``3.10``, and *Sphinx*
``6``)
.. code-block::
$> python3 -m tox -e py310-sphinx6-integration -- \
-p tests/unit
Documentation
-------------
.. code-block::
$> python3 -m pipenv run doc

View file

@ -7,7 +7,7 @@ requires = [
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "byteb4rb1e.sphinxcontrib.ext" name = "byteb4rb1e.sphinxcontrib"
description = "" description = ""
authors = [ authors = [
{ name = "Tiara Rodney", email = "tiara.rodney@byteb4rb1e.me" } { name = "Tiara Rodney", email = "tiara.rodney@byteb4rb1e.me" }
@ -30,17 +30,19 @@ classifiers = [
] ]
dependencies = [ dependencies = [
"sphinx>=5.1", "sphinx>=5.1",
"byteb4rb1e.utils @ git+https://bitbucket.org/byteb4rb1e/py-utils.git@32ae99c5fa0174761f4053fce1130f3cd7a2a68b"
] ]
dynamic = ["version"] dynamic = ["version"]
requires-python = ">=3.8" requires-python = ">=3.8"
[project.optional-dependencies]
git = ["pygit2>=1.18.0,<2",]
[project.urls] [project.urls]
Bitbucket = "https://bitbucket.org/byteb4rb1e/sphinxcontrib" Bitbucket = "https://bitbucket.org/byteb4rb1e/sphinxcontrib"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]
namespaces = true
[tool.autopep8] [tool.autopep8]

View file

@ -2,7 +2,7 @@
alabaster==1.0.0 alabaster==1.0.0
babel==2.17.0 babel==2.17.0
build==1.2.2.post1 build==1.2.2.post1
cachetools==5.5.2 cachetools==6.0.0
certifi==2025.4.26 certifi==2025.4.26
chardet==5.2.0 chardet==5.2.0
charset-normalizer==3.4.2 charset-normalizer==3.4.2
@ -37,7 +37,7 @@ requests-toolbelt==1.0.0
rfc3986==2.0.0 rfc3986==2.0.0
rich==14.0.0 rich==14.0.0
roman-numerals-py==3.1.0 roman-numerals-py==3.1.0
setuptools==80.7.1 setuptools==80.8.0
setuptools-scm==8.3.1 setuptools-scm==8.3.1
snowballstemmer==3.0.1 snowballstemmer==3.0.1
sphinx==8.2.3 sphinx==8.2.3

View file

@ -1,7 +1,7 @@
-i https://pypi.org/simple -i https://pypi.org/simple
alabaster==1.0.0 alabaster==1.0.0
babel==2.17.0 babel==2.17.0
-e . byteb4rb1e.sphinxcontrib==0.1.dev5+g3c348d5.d20250526
certifi==2025.4.26 certifi==2025.4.26
charset-normalizer==3.4.2 charset-normalizer==3.4.2
colorama==0.4.6 colorama==0.4.6

View file

@ -0,0 +1,7 @@
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
def setup(app: Sphinx) -> ExtensionMetadata:
"""add this extension and its children to a Sphinx application"""
return {}

View file

@ -1,86 +0,0 @@
import importlib.resources
import mimetypes
from pathlib import Path
import urllib.request
import tarfile
from typing import Tuple, Optional, Dict
from io import IOBase
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.util.logging import getLogger
from byteb4rb1e.utils.urllib.request import PkgHandler
logger = getLogger(__name__)
_static_archives: Dict[str, IOBase] = {}
_opener: urllib.request.OpenerDirector = urllib.request.build_opener(
PkgHandler()
)
def on_config_inited(
app: Sphinx,
env: BuildEnvironment
):
"""
"""
global _static_archives
static_archives = app.config['html_static_archive']
if not static_archives:
return
if isinstance(static_archives, str):
static_archives = [static_archives]
for url in static_archives:
_static_archives[url] = _opener.open(url)
def on_env_updated(
app: Sphinx,
env: BuildEnvironment
):
"""
"""
static_dir = Path(app.outdir) / '_static'
for url, fh in _static_archives.items():
modes = {
'application/x-tar': 'r',
'application/x-tar+gzip': 'r:gz',
'application/x-tar+bzip2': 'r:bz2',
'application/x-tar+xz': 'r:xz',
}
mime_type = fh.headers.get('Content-Type')
if mime_type not in modes.keys():
raise Exception('')
archive = tarfile.open(fileobj=fh, mode=modes[mime_type])
for file in archive.getmembers():
if file.isdir():
continue
shadow_file = static_dir / file.name
if not shadow_file.exists():
logger.info(f'html_static_archive (extract): {url}::{file.name}')
archive.extract(file, path=static_dir, filter='data')
archive.close()
fh.close()
def setup(app: Sphinx):
app.add_config_value('html_static_archive', [], True)
app.connect('env-check-consistency', on_config_inited)
app.connect('env-check-consistency', on_env_updated)

View file

@ -1,108 +0,0 @@
from collections import namedtuple
import json
import logging
import os
from pathlib import Path
import shutil
from typing import Any, Tuple, Callable, Iterator, Dict
import warnings
import pytest
try:
from sphinx.deprecation import RemovedInSphinx90Warning
except ImportError:
RemovedInSphinx90Warning = None
from sphinx.testing.util import SphinxTestApp
from sphinx.testing.fixtures import make_app, test_params
USE_PATHLIB = True
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
from sphinx.testing.path import path as sphinxtesting_path
if w and issubclass(w[-1].message.__class__, RemovedInSphinx90Warning):
USE_PATHLIB = True
else:
USE_PATHLIB = False
from byteb4rb1e.testing.pytest.fixtures import current_test
_SPHINX_TESTAPP_COUNTER: Dict[str, int] = {}
_SPHINX_TESTSRC_COUNTER: Dict[str, int] = {}
@pytest.fixture
def mock_sphinx_testsrc_dir(
tmp_path: Path,
):
global _SPHINX_TESTSRC_COUNTER
_SPHINX_TESTSRC_COUNTER.setdefault(tmp_path, 0)
testsrc_id = _SPHINX_TESTSRC_COUNTER[tmp_path]
_SPHINX_TESTSRC_COUNTER[tmp_path] += 1
srcdir = tmp_path / f'sphinx-testsrc-{testsrc_id}'
def wrap():
srcdir.mkdir(parents=True)
(srcdir / 'conf.py').write_text("""
project = 'foobar'
master_doc = 'index'
""")
(srcdir / 'index.rst').write_text("""
######
Foobar
######
Hello world!
""")
return srcdir
return wrap
@pytest.fixture
def mock_sphinx_testapp(
make_app: Callable[[], SphinxTestApp],
tmp_path: Path,
caplog
):
global _SPHINX_TESTAPP_COUNTER
_SPHINX_TESTAPP_COUNTER.setdefault(tmp_path, 0)
testapp_id = _SPHINX_TESTAPP_COUNTER[tmp_path]
_SPHINX_TESTAPP_COUNTER[tmp_path] += 1
"""Provides the 'sphinx.application.Sphinx' object"""
def wrap(*args, **kwargs) -> Iterator[SphinxTestApp]:
assert kwargs.get('srcdir'), 'srcdir keyword argument missing'
caplog.set_level(logging.DEBUG)
logger = logging.getLogger()
basedir = tmp_path / f'sphinx-testapp-{testapp_id}'
srcdir = basedir / 'src'
builddir = basedir / 'build'
shutil.copytree(kwargs['srcdir'], srcdir, dirs_exist_ok=True)
kwargs['srcdir'] = sphinxtesting_path(srcdir) if not USE_PATHLIB else srcdir
kwargs.setdefault('builddir', sphinxtesting_path(builddir) if not USE_PATHLIB else builddir)
app_ = make_app(*args, **kwargs)
logger.debug(json.dumps({
'builder': app_.builder.name,
'srcdir': str(app_.srcdir),
'outdir': str(app_.outdir)
}, indent = 4))
return app_
return wrap

View file

@ -1,117 +0,0 @@
from pathlib import Path
import tarfile
import pytest
from byteb4rb1e.sphinxcontrib.testing.pytest.fixtures import (
mock_sphinx_testapp,
mock_sphinx_testsrc_dir
)
from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once
from byteb4rb1e.testing.pytest.fixtures import mock_system_site_package_dir
@run_in_subprocess_once()
def test_default(
mock_sphinx_testapp,
mock_sphinx_testsrc_dir,
mock_system_site_package_dir,
tmp_path,
) -> None:
"""
"""
pkg_dir = mock_system_site_package_dir('dummypkg')
mock_static_archive = tarfile.open(pkg_dir / 'data.tar', 'w')
(tmp_path / 'foobar.txt').write_text("Hello world!")
mock_static_archive.addfile(
mock_static_archive.gettarinfo(
arcname='foobar.txt',
fileobj=(tmp_path / 'foobar.txt').open('r')
)
)
mock_static_archive.close()
app = mock_sphinx_testapp(srcdir=mock_sphinx_testsrc_dir())
app.setup_extension('byteb4rb1e.sphinxcontrib.ext.html_static_archive')
app.config['html_static_archive'] = f'pkg://dummypkg/data.tar'
app.build(force_all=True)
assert (Path(app.outdir) / '_static' / 'foobar.txt').exists()
@run_in_subprocess_once()
def test_compression(
mock_sphinx_testapp,
mock_sphinx_testsrc_dir,
mock_system_site_package_dir,
tmp_path,
) -> None:
"""
"""
pkg_dir = mock_system_site_package_dir('dummypkg')
mock_static_archive = tarfile.open(pkg_dir / 'data.tar.gz', 'w:gz')
(tmp_path / 'foobar.txt').write_text("Hello world!")
mock_static_archive.addfile(
mock_static_archive.gettarinfo(
arcname='foobar.txt',
fileobj=(tmp_path / 'foobar.txt').open('r')
)
)
mock_static_archive.close()
app = mock_sphinx_testapp(srcdir=mock_sphinx_testsrc_dir())
app.setup_extension('byteb4rb1e.sphinxcontrib.ext.html_static_archive')
app.config['html_static_archive'] = f'pkg://dummypkg/data.tar.gz'
app.build(force_all=True)
assert (Path(app.outdir) / '_static' / 'foobar.txt').exists()
@run_in_subprocess_once()
def test_subdirs(
mock_sphinx_testapp,
mock_sphinx_testsrc_dir,
mock_system_site_package_dir,
tmp_path,
) -> None:
"""
"""
pkg_dir = mock_system_site_package_dir('dummypkg')
mock_static_archive = tarfile.open(pkg_dir / 'data.tar.gz', 'w:gz')
(tmp_path / 'foobar.txt').write_text("Hello world!")
mock_static_archive.addfile(
mock_static_archive.gettarinfo(
arcname='foo/bar/foobar.txt',
fileobj=(tmp_path / 'foobar.txt').open('r')
)
)
mock_static_archive.close()
app = mock_sphinx_testapp(srcdir=mock_sphinx_testsrc_dir())
app.setup_extension('byteb4rb1e.sphinxcontrib.ext.html_static_archive')
app.config['html_static_archive'] = f'pkg://dummypkg/data.tar.gz'
app.build(force_all=True)
assert (Path(app.outdir) / '_static' / 'foo' / 'bar' / 'foobar.txt').exists()

View file

@ -1,32 +0,0 @@
from pathlib import Path
import pytest
pytestmark = pytest.mark.pytest
from byteb4rb1e.sphinxcontrib.testing.pytest.fixtures import (
mock_sphinx_testapp,
mock_sphinx_testsrc_dir
)
def test_mock_sphinx_testsrc_dir(mock_sphinx_testsrc_dir):
"""
"""
srcdir = mock_sphinx_testsrc_dir()
assert (srcdir / 'conf.py').exists()
assert (srcdir / 'index.rst').exists()
def test_mock_sphinx_testapp(mock_sphinx_testapp, mock_sphinx_testsrc_dir):
"""
"""
srcdir = mock_sphinx_testsrc_dir()
app = mock_sphinx_testapp(srcdir=srcdir)
assert app.builder is not None
assert Path(app.srcdir).samefile(srcdir) is False # Should be copied to internal src
app.build(force_all=True)

View file

@ -0,0 +1,2 @@
def test_default() -> None:
assert 1 == 1

View file

@ -1,17 +0,0 @@
from pathlib import Path
import pytest
pytest_plugins = ['byteb4rb1e.sphinxcontrib.testing.pytest.fixtures']
_TESTS_ROOT = Path(__file__).resolve().parent
def pytest_configure(config):
# register an additional marker
config.addinivalue_line(
"markers", "pytest: test pytest integration"
)
@pytest.fixture(scope='session')
def rootdir() -> Path:
return _TESTS_ROOT

34
tox.ini
View file

@ -2,9 +2,8 @@
requires = requires =
tox>=4.19 tox>=4.19
env_list = env_list =
unit-py3{9-13} py3{8-12}-{unit}
integration-py3{9-13}-sphinx{5-8} py3{8-12}-sphinx{5-8}-{integration}
integration-py3{9-13}-sphinx{5-8}-pytest8
lint lint
format format
@ -36,17 +35,17 @@ deps =
commands = commands =
black --check src tests black --check src tests
[testenv:unit-py3{9-13}] [testenv:py3{9-13}-unit]
description = run type check on code base description = run type check on code base
labels = unit labels = unit
deps = deps =
{[testenv]deps} {[testenv]deps}
pytest pytest
commands = commands =
pytest tests/unit --junitxml=test-reports/{env_name}.xml {posargs} pytest tests/unit --junitxml=test-reports/{env_name}.xml
[testenv:integration-py3{9-13}-sphinx{5-7}] [testenv:py3{9-13}-sphinx{5-7}-integration]
description = run sphinx 5-7 integration tests description = run type check on code base
labels = integration labels = integration
deps = deps =
{[testenv]deps} {[testenv]deps}
@ -55,27 +54,14 @@ deps =
sphinx7: sphinx>=7.0,<=8.0 sphinx7: sphinx>=7.0,<=8.0
pytest pytest
commands = commands =
pytest tests/integration --junitxml=test-reports/{env_name}.xml {posargs} pytest tests/integration --junitxml=test-reports/{env_name}.xml
[testenv:integration-py3{10-13}-sphinx8] [testenv:py3{10-13}-sphinx8-integration]
description = run sphinx 8 integration tests description = run type check on code base
labels = integration labels = integration
deps = deps =
{[testenv]deps} {[testenv]deps}
sphinx8: sphinx>=8.0,<=9.0 sphinx8: sphinx>=8.0,<=9.0
pytest pytest
commands = commands =
pytest tests/integration --junitxml=test-reports/{env_name}.xml {posargs} pytest tests/integration --junitxml=test-reports/{env_name}.xml
[testenv:integration-py3{10-13}-sphinx{5-8}-pytest8]
description = run pytest 8 testing integration tests (excluding Python 3.9)
labels = integration
deps =
{[testenv]deps}
sphinx5: sphinx>=5.0,<=6.0
sphinx6: sphinx>=6.0,<=7.0
sphinx7: sphinx>=7.0,<=8.0
sphinx8: sphinx>=8.0,<=9.0
pytest8: pytest>=8.0,<=9.0
commands =
pytest tests/integration -m pytest --junitxml=test-reports/{env_name}.xml {posargs}