Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]
sphinx-version: ["6", "7", "8"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -34,16 +35,17 @@ jobs:
run : |
python -m pip install --upgrade pip
pip install -e.[testing]
pip install "sphinx>=${{ matrix.sphinx-version }},<${{ matrix.sphinx-version == '6' && '7' || matrix.sphinx-version == '7' && '8' || '9' }}"
- name: Run pytest
run: |
pytest --durations=10 --cov=sphinx_exercise --cov-report=xml --cov-report=term-missing
- name: Create cov
run: coverage xml
- name: Upload to Codecov
if: false && (matrix.python-version == '3.11')
if: false && (matrix.python-version == '3.11' && matrix.sphinx-version == '8')
uses: codecov/codecov-action@v4
with:
name: sphinx-exercise-pytest-py3.11
name: sphinx-exercise-pytest-py3.11-sphinx8
token: "${{ secrets.CODECOV_TOKEN }}"
flags: pytests
file: ./coverage.xml
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
# Changelog


## [v1.0.1](https://github.com/executablebooks/sphinx-exercise/tree/v1.0.1) (2024-07-01)

### Improved 👌

- Updates to testing infrastructure and minor bug fixes
- Fixed JupyterBuilder handling in test suite

## [v1.0.0](https://github.com/executablebooks/sphinx-exercise/tree/v1.0.0) (2024-01-15)

### Improved 👌

- Added support for Sphinx 7
- Updated build and CI infrastructure
- Fixed deprecation warnings for Sphinx 7 compatibility
- Improved type hints throughout codebase
- Fixed doctree node mutation issues by copying nodes
- Updated codecov to version 3
- Pinned matplotlib to 3.7.* for testing stability

## [v0.4.1](https://github.com/executablebooks/sphinx-exercise/tree/v0.4.1) (2023-1-23)

### Improved 👌
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ recursive-include sphinx_exercise *.css
recursive-include sphinx_exercise *.json
recursive-include sphinx_exercise *.mo
recursive-include sphinx_exercise *.po
recursive-include sphinx_exercise *.py
recursive-include sphinx_exercise *.py
19 changes: 9 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dynamic = ["version"]
description = "A Sphinx extension for producing exercises and solutions."
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
requires-python = ">=3.11"
authors = [
{ name = "QuantEcon", email = "[email protected]" },
]
Expand All @@ -22,10 +22,9 @@ classifiers = [
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"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 :: Documentation",
"Topic :: Documentation :: Sphinx",
"Topic :: Software Development :: Documentation",
Expand All @@ -34,7 +33,7 @@ classifiers = [
]
dependencies = [
"sphinx-book-theme",
"sphinx>=5",
"sphinx>=6.1",
]

[project.optional-dependencies]
Expand All @@ -49,20 +48,20 @@ code_style = [
"pre-commit",
]
rtd = [
"myst-nb~=1.0.0",
"myst-nb>=1.1.0",
"sphinx-book-theme",
"sphinx_togglebutton",
"sphinx>=5,<8",
"sphinx>=6.1,<9",
]
testing = [
"beautifulsoup4",
"coverage",
"matplotlib==3.8.*",
"myst-nb~=1.0.0",
"matplotlib>=3.8",
"myst-nb>=1.1.0",
"pytest-cov",
"pytest-regressions",
"pytest~=8.0.0",
"sphinx>=5,<8",
"pytest>=8.0",
"sphinx>=6.1,<9",
"texsoup",
"defusedxml", # Required by sphinx-testing
]
Expand Down
8 changes: 4 additions & 4 deletions sphinx_exercise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

__version__ = "1.0.1"

import os
from pathlib import Path
from typing import Any, Dict, Set, Union, cast
from sphinx.config import Config
Expand Down Expand Up @@ -71,6 +70,7 @@

# Callback Functions


def purge_exercises(app: Sphinx, env: BuildEnvironment, docname: str) -> None:
"""Purge sphinx_exercise registry"""

Expand Down Expand Up @@ -214,9 +214,9 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_css_file("exercise.css")

# add translations
package_dir = os.path.abspath(os.path.dirname(__file__))
locale_dir = os.path.join(package_dir, "translations", "locales")
app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir)
package_dir = Path(__file__).parent.resolve()
locale_dir = package_dir / "translations" / "locales"
app.add_message_catalog(MESSAGE_CATALOG_NAME, str(locale_dir))

return {
"version": "builtin",
Expand Down
24 changes: 13 additions & 11 deletions sphinx_exercise/directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,30 @@
:licences: see LICENSE for details
"""

from pathlib import Path
from typing import List
from docutils.nodes import Node

from sphinx.util.docutils import SphinxDirective
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst import directives
from sphinx.locale import get_translation
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective

from .nodes import (
exercise_node,
exercise_enumerable_node,
exercise_end_node,
exercise_enumerable_node,
exercise_node,
exercise_subtitle,
exercise_title,
solution_end_node,
solution_node,
solution_start_node,
solution_end_node,
exercise_title,
exercise_subtitle,
solution_title,
)
from docutils import nodes
from sphinx.util import logging

logger = logging.getLogger(__name__)

from sphinx.locale import get_translation
MESSAGE_CATALOG_NAME = "exercise"
translate = get_translation(MESSAGE_CATALOG_NAME)

Expand All @@ -40,7 +42,7 @@ def duplicate_labels(self, label):

if not label == "" and label in self.env.sphinx_exercise_registry.keys():
docpath = self.env.doc2path(self.env.docname)
path = docpath[: docpath.rfind(".")]
path = str(Path(docpath).with_suffix(""))
other_path = self.env.doc2path(
self.env.sphinx_exercise_registry[label]["docname"]
)
Expand Down
8 changes: 5 additions & 3 deletions sphinx_exercise/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
from docutils import nodes as docutil_nodes
from sphinx import addnodes as sphinx_nodes
from sphinx.writers.latex import LaTeXTranslator
from sphinx.locale import get_translation
from .latex import LaTeXMarkup

logger = logging.getLogger(__name__)
LaTeX = LaTeXMarkup()


from sphinx.locale import get_translation
MESSAGE_CATALOG_NAME = "exercise"
translate = get_translation(MESSAGE_CATALOG_NAME)

Expand Down Expand Up @@ -54,7 +53,10 @@ class solution_end_node(docutil_nodes.Admonition, docutil_nodes.Element):
class exercise_title(docutil_nodes.title):
def default_title(self):
title_text = self.children[0].astext()
if title_text == f"{translate('Exercise')}" or title_text == f"{translate('Exercise')} %s":
if (
title_text == f"{translate('Exercise')}"
or title_text == f"{translate('Exercise')} %s"
):
return True
else:
return False
Expand Down
4 changes: 3 additions & 1 deletion sphinx_exercise/post_transforms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from pathlib import Path

import sphinx.addnodes as sphinx_nodes
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
Expand Down Expand Up @@ -195,7 +197,7 @@ def run(self):
except AttributeError:
docname = self.env.docname # for builder such as JupyterBuilder that don't support current_docname
docpath = self.env.doc2path(docname)
path = docpath[: docpath.rfind(".")]
path = str(Path(docpath).with_suffix(""))
msg = f"undefined label: {target_label}"
logger.warning(msg, location=path, color="red")
return
Expand Down
14 changes: 10 additions & 4 deletions sphinx_exercise/translations/_convert.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import json
import os
from pathlib import Path
import subprocess

MESSAGE_CATALOG_NAME = "exercise"


def convert_json(folder=None):
folder = folder or Path(__file__).parent

Expand All @@ -19,7 +19,13 @@ def convert_json(folder=None):
english = data[0]["text"]
for item in data[1:]:
language = item["symbol"]
out_path = folder / "locales" / language / "LC_MESSAGES" / f"{MESSAGE_CATALOG_NAME}.po"
out_path = (
folder
/ "locales"
/ language
/ "LC_MESSAGES"
/ f"{MESSAGE_CATALOG_NAME}.po"
)
if not out_path.parent.exists():
out_path.parent.mkdir(parents=True)
if not out_path.exists():
Expand Down Expand Up @@ -47,9 +53,9 @@ def convert_json(folder=None):
subprocess.check_call(
[
"msgfmt",
os.path.abspath(path),
str(path.resolve()),
"-o",
os.path.abspath(path.parent / f"{MESSAGE_CATALOG_NAME}.mo"),
str((path.parent / f"{MESSAGE_CATALOG_NAME}.mo").resolve()),
]
)

Expand Down
4 changes: 2 additions & 2 deletions tests/books/test-gateddirective/solution-exercise-gated.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ axs[0].set_xlabel('time')
axs[0].set_ylabel('s1 and s2')
axs[0].grid(True)

cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt)
cxy, f = axs[1].cohere(s1, s2, NFFT=256, Fs=1. / dt)
axs[1].set_ylabel('coherence')

fig.tight_layout()
Expand Down Expand Up @@ -87,7 +87,7 @@ axs[0].set_xlabel('time')
axs[0].set_ylabel('s1 and s2')
axs[0].grid(True)

cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt)
cxy, f = axs[1].cohere(s1, s2, NFFT=256, Fs=1. / dt)
axs[1].set_ylabel('coherence')

fig.tight_layout()
Expand Down
4 changes: 2 additions & 2 deletions tests/books/test-gateddirective/solution-exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ axs[0].set_xlabel('time')
axs[0].set_ylabel('s1 and s2')
axs[0].grid(True)

cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt)
cxy, f = axs[1].cohere(s1, s2, NFFT=256, Fs=1. / dt)
axs[1].set_ylabel('coherence')

fig.tight_layout()
Expand Down Expand Up @@ -85,7 +85,7 @@ axs[0].set_xlabel('time')
axs[0].set_ylabel('s1 and s2')
axs[0].grid(True)

cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt)
cxy, f = axs[1].cohere(s1, s2, NFFT=256, Fs=1. / dt)
axs[1].set_ylabel('coherence')

fig.tight_layout()
Expand Down
7 changes: 6 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
def rootdir(tmpdir):
from sphinx.testing.path import path

src = path(__file__).parent.absolute() / "books"
# In Sphinx 6, path objects don't have .absolute() method, but they are already absolute
src = path(__file__).parent / "books"
dst = tmpdir.join("books")
shutil.copytree(src, dst)
yield path(dst)
Expand Down Expand Up @@ -94,6 +95,10 @@ class FileRegression:
(r"original_uri=\"[^\"]*\"\s", ""),
# TODO: Remove when support for Sphinx<7.2 is dropped
("Link to", "Permalink to"),
# Strip ipykernel process IDs (temporary directory paths)
(r"ipykernel_\d+", "ipykernel_XXXXX"),
# Normalize matplotlib image hashes (platform/version dependent)
(r"[a-f0-9]{64}\.png", "IMAGEHASH.png"),
)

def __init__(self, file_regression):
Expand Down
5 changes: 4 additions & 1 deletion tests/test_exercise_references.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from bs4 import BeautifulSoup
import pytest
import sphinx

SPHINX_VERSION = f".sphinx{sphinx.version_info[0]}"


@pytest.mark.sphinx("html", testroot="mybook")
Expand Down Expand Up @@ -35,7 +38,7 @@ def test_reference(app, idir, file_regression):
excs += f"{ref}\n"

file_regression.check(
str(excs[:-1]), basename=idir.split(".")[0], extension=".html"
str(excs[:-1]), basename=idir.split(".")[0], extension=f"{SPHINX_VERSION}.html"
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>This is a reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">Exercise 1</span></a>.</p>
<p>This is a second reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text 1</span></a>.</p>
<p>This is a third reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text 1</span></a>.</p>
<p>This is a fourth reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text Exercise</span></a>.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>This is a reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">Exercise 1</span></a>.</p>
<p>This is a second reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text 1</span></a>.</p>
<p>This is a third reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text 1</span></a>.</p>
<p>This is a fourth reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text Exercise</span></a>.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>This is a reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">Exercise 1</span></a>.</p>
<p>This is a second reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text 1</span></a>.</p>
<p>This is a third reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text 1</span></a>.</p>
<p>This is a fourth reference <a class="reference internal" href="_enum_mathtitle_label.html#test-exc-label-math"><span class="std std-numref">some text Exercise</span></a>.</p>
<p class="topless"><a href="_enum_numref_title.html" title="previous chapter">_enum_numref_title</a></p>
<p class="topless"><a href="_unenum_mathtitle_label.html" title="next chapter">_unenum_mathtitle_label</a></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>This is a reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">Exercise 2</span></a>.</p>
<p>This is a second reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text 2</span></a>.</p>
<p>This is a third reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text 2</span></a>.</p>
<p>This is a fourth reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text Exercise</span></a>.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>This is a reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">Exercise 2</span></a>.</p>
<p>This is a second reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text 2</span></a>.</p>
<p>This is a third reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text 2</span></a>.</p>
<p>This is a fourth reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text Exercise</span></a>.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>This is a reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">Exercise 2</span></a>.</p>
<p>This is a second reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text 2</span></a>.</p>
<p>This is a third reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text 2</span></a>.</p>
<p>This is a fourth reference <a class="reference internal" href="_enum_notitle_label.html#text-exc-notitle"><span class="std std-numref">some text Exercise</span></a>.</p>
<p class="topless"><a href="_enum_ref_mathtitle.html" title="previous chapter">_enum_ref_mathtitle</a></p>
<p class="topless"><a href="_enum_numref_title.html" title="next chapter">_enum_numref_title</a></p>
Loading