diff --git a/.flake8 b/.flake8 index 492c8c2..0e24510 100644 --- a/.flake8 +++ b/.flake8 @@ -13,6 +13,9 @@ ignore = # E303 too many blank lines E303, + # PT007 wrong values type in @pytest.mark.parametrize, expected list + PT007, + max-line-length = 120 exclude = diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..0f97d87 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,15 @@ +name: Pre-Commit + +on: + pull_request: + branches: [main, master] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index f1d9d29..4020d8d 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -9,13 +9,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: main - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Install setuptools run: | diff --git a/.github/workflows/run_tox.yml b/.github/workflows/run_tox.yml index ad69a07..7136e72 100644 --- a/.github/workflows/run_tox.yml +++ b/.github/workflows/run_tox.yml @@ -8,12 +8,12 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90daabe..1cb5edf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,36 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.4.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort name: isort (python) - - repo: https://gitlab.com/PyCQA/flake8 - rev: '3.9.2' + + - repo: https://github.com/PyCQA/flake8 + rev: '6.0.0' hooks: - id: flake8 + additional_dependencies: + - flake8-bugbear==23.1.20 + - flake8-comprehensions==3.10.1 + - flake8-pytest-style==1.6 + - flake8-noqa==1.3 + - pep8-naming==0.13.3 + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks + + + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes diff --git a/readme.md b/readme.md index ddd5744..63b1083 100644 --- a/readme.md +++ b/readme.md @@ -36,27 +36,30 @@ This code will be executed ``` # Changelog -#### 0.8 (18.07.2022) +#### 0.9 (2023-02-08) +- If the whole shown code block is indented the indention is removed + +#### 0.8 (2022-07-18) - Renamed ``exec_code_folders`` to ``exec_code_source_folders`` - Changed type of parameter to specify stdout to a flag - Changed default for config parameter that sets encoding - Dropped support for Python 3.7 -#### 0.7 (15.07.2022) +#### 0.7 (2022-07-15) - Added config parameter to specify stdout encoding - Only empty lines of the output get trimmed -#### 0.6 (04.04.2022) +#### 0.6 (2022-04-04) - Fixed an issue where the line numbers for error messages were not correct -#### 0.5 (10.03.2022) +#### 0.5 (2022-03-10) - Marked as safe for parallel reading -#### 0.4 (09.03.2022) +#### 0.4 (2022-03-09) - Added option to run code from example files -#### 0.3 (24.09.2021) +#### 0.3 (2021-09-24) - Added some more documentation and fixed some false path warnings -#### 0.2 (21.09.2021) +#### 0.2 (2021-09-21) - Initial Release diff --git a/requirements.txt b/requirements.txt index e24fd58..40e8301 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -pytest==7.1.2 -pre-commit==2.20.0 -sphinx==5.0.2 -sphinx-rtd-theme==1.0.0 +pytest >= 7.2, < 7.3 +pre-commit >= 3.0, < 3.1 + +sphinx >= 6.1, < 6.2 +sphinx-rtd-theme >= 1.2, <1.3 diff --git a/setup.py b/setup.py index d233fc1..afcd1ae 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ def load_version() -> str: "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only", ], ) diff --git a/src/sphinx_exec_code/__version__.py b/src/sphinx_exec_code/__version__.py index 376df7c..e46aee1 100644 --- a/src/sphinx_exec_code/__version__.py +++ b/src/sphinx_exec_code/__version__.py @@ -1 +1 @@ -__version__ = '0.8' +__version__ = '0.9' diff --git a/src/sphinx_exec_code/code_exec.py b/src/sphinx_exec_code/code_exec.py index 7f35de9..b2e9ac2 100644 --- a/src/sphinx_exec_code/code_exec.py +++ b/src/sphinx_exec_code/code_exec.py @@ -4,7 +4,7 @@ from itertools import dropwhile from pathlib import Path -from sphinx_exec_code.code_exec_error import CodeException +from sphinx_exec_code.code_exec_error import CodeExceptionError from sphinx_exec_code.configuration import PYTHONPATH_FOLDERS, SET_UTF8_ENCODING, WORKING_DIR @@ -25,7 +25,7 @@ def execute_code(code: str, file: Path, first_loc: int) -> str: encoding=encoding, cwd=cwd, env=env) if run.returncode != 0: - raise CodeException(code, file, first_loc, run.returncode, run.stderr) from None + raise CodeExceptionError(code, file, first_loc, run.returncode, run.stderr) from None # decode output and drop tailing spaces ret_str = (run.stdout if run.stdout is not None else '' + run.stderr if run.stderr is not None else '').rstrip() diff --git a/src/sphinx_exec_code/code_exec_error.py b/src/sphinx_exec_code/code_exec_error.py index 4d1e6f0..5ff4343 100644 --- a/src/sphinx_exec_code/code_exec_error.py +++ b/src/sphinx_exec_code/code_exec_error.py @@ -5,7 +5,7 @@ re_line = re.compile(r'^\s*File "()", line (\d+), in ', re.MULTILINE) -class CodeException(Exception): +class CodeExceptionError(Exception): def __init__(self, code: str, file: Path, first_loc: int, ret: int, stderr: str): self.code = code diff --git a/src/sphinx_exec_code/code_format.py b/src/sphinx_exec_code/code_format.py index dc46d7f..dad23c3 100644 --- a/src/sphinx_exec_code/code_format.py +++ b/src/sphinx_exec_code/code_format.py @@ -1,4 +1,4 @@ -from typing import Iterable, Tuple, List +from typing import Iterable, List, Tuple class VisibilityMarkerError(Exception): @@ -64,10 +64,24 @@ def get_show_exec_code(code_lines: Iterable[str]) -> Tuple[str, str]: if skip.is_marker(line): continue - hide.add_line(org_line) - skip.add_line(org_line) + add_line = org_line.rstrip() + hide.add_line(add_line) + skip.add_line(add_line) - shown_code = '\n'.join(hide.lines) + # remove leading and tailing empty lines of the shown code + shown_lines = hide.lines + while shown_lines and not shown_lines[0].strip(): + shown_lines.pop(0) + while shown_lines and not shown_lines[-1].strip(): + shown_lines.pop(-1) + + # check if the shown code block is indented as a whole -> strip + leading_spaces = [len(line) - len(line.lstrip()) for line in shown_lines] + if strip_spaces := min(leading_spaces): + for i, line in enumerate(shown_lines): + shown_lines[i] = line[strip_spaces:] + + shown_code = '\n'.join(shown_lines) executed_code = '\n'.join(skip.lines) - return shown_code.strip(), executed_code.strip() + return shown_code, executed_code.strip() diff --git a/src/sphinx_exec_code/configuration/path_config.py b/src/sphinx_exec_code/configuration/path_config.py index 05737e3..31e9d6e 100644 --- a/src/sphinx_exec_code/configuration/path_config.py +++ b/src/sphinx_exec_code/configuration/path_config.py @@ -7,6 +7,10 @@ from sphinx_exec_code.configuration.base import SphinxConfigValue, TYPE_VALUE +class InvalidPathError(Exception): + pass + + class SphinxConfigPath(SphinxConfigValue[TYPE_VALUE]): SPHINX_TYPE = (str, Path) @@ -14,8 +18,8 @@ def make_path(self, app: SphinxApp, value) -> Path: try: path = Path(value) except Exception: - raise ValueError(f'Could not create Path from "{value}" (type {type(value).__name__}) ' - f'(configured by {self.sphinx_name:s})') from None + raise InvalidPathError(f'Could not create Path from "{value}" (type {type(value).__name__}) ' + f'(configured by {self.sphinx_name:s})') from None if not path.is_absolute(): path = (Path(app.confdir) / path).resolve() diff --git a/src/sphinx_exec_code/sphinx_exec.py b/src/sphinx_exec_code/sphinx_exec.py index e8b7e83..a425e23 100644 --- a/src/sphinx_exec_code/sphinx_exec.py +++ b/src/sphinx_exec_code/sphinx_exec.py @@ -7,7 +7,7 @@ from sphinx.util.docutils import SphinxDirective from sphinx_exec_code.__const__ import log -from sphinx_exec_code.code_exec import CodeException, execute_code +from sphinx_exec_code.code_exec import CodeExceptionError, execute_code from sphinx_exec_code.code_format import get_show_exec_code, VisibilityMarkerError from sphinx_exec_code.configuration import EXAMPLE_DIR from sphinx_exec_code.sphinx_spec import build_spec, SpecCode, SpecOutput, SphinxSpecBase @@ -63,7 +63,7 @@ def _get_code_line(self, line_no: int, content: List[str]) -> int: i = 0 first_line = content[0] - for i, raw_line in enumerate(self.block_text.splitlines()): + for i, raw_line in enumerate(self.block_text.splitlines()): # noqa: B007 # raw line contains the leading white spaces if raw_line.lstrip() == first_line: break @@ -100,7 +100,7 @@ def _run(self) -> list: try: code_results = execute_code(code_exec, file, line) - except CodeException as e: + except CodeExceptionError as e: # Newline so we don't have the build message mixed up with logs print() diff --git a/tests/test_code_exec.py b/tests/test_code_exec.py index d387b68..31a16f7 100644 --- a/tests/test_code_exec.py +++ b/tests/test_code_exec.py @@ -3,17 +3,16 @@ import pytest -from sphinx_exec_code.code_exec import CodeException, execute_code +from sphinx_exec_code.code_exec import CodeExceptionError, execute_code from sphinx_exec_code.configuration import PYTHONPATH_FOLDERS, SET_UTF8_ENCODING, WORKING_DIR -@pytest.fixture +@pytest.fixture() def setup_env(monkeypatch): f = Path(__file__).parent monkeypatch.setattr(WORKING_DIR, '_value', f) monkeypatch.setattr(PYTHONPATH_FOLDERS, '_value', [str(f)]) - - yield + return None @pytest.mark.parametrize('utf8', [True, False]) @@ -42,7 +41,7 @@ def test_err(setup_env, monkeypatch, utf8): code = "print('Line1')\nprint('Line2')\n1/0" - with pytest.raises(CodeException) as e: + with pytest.raises(CodeExceptionError) as e: execute_code(code, Path('/my_file'), 5) msg = e.value.pformat() diff --git a/tests/test_code_format.py b/tests/test_code_format.py index dc4ff4b..ebe024f 100644 --- a/tests/test_code_format.py +++ b/tests/test_code_format.py @@ -7,7 +7,7 @@ def test_format_hide(): code = 'print("1")\n# - hide: start - \nprint("2")\n #hide:stop\n \n \nprint("3")' show, run = get_show_exec_code(code.splitlines()) assert show == 'print("1")\nprint("3")' - assert run == 'print("1")\nprint("2")\n \n \nprint("3")' + assert run == 'print("1")\nprint("2")\n\n\nprint("3")' def test_format_skip(): @@ -18,6 +18,22 @@ def test_format_skip(): def test_marker_err(): + code = 'print("1")\n# - hide: start - \n# - hide: start - \nprint("2")\n #hide:stop\nprint("3")' with pytest.raises(VisibilityMarkerError): - code = 'print("1")\n# - hide: start - \n# - hide: start - \nprint("2")\n #hide:stop\nprint("3")' - _, _ = get_show_exec_code(code.splitlines()) + get_show_exec_code(code.splitlines()) + + +def test_code_indent(): + code = """ + + print('asdf') + print('1234') + # comment + + + """ + show, run = get_show_exec_code(code.splitlines()) + + assert show == "print('asdf')\n" \ + " print('1234')\n" \ + " # comment" diff --git a/tests/test_sphinx_config.py b/tests/test_sphinx_config.py index 424918e..4b7827f 100644 --- a/tests/test_sphinx_config.py +++ b/tests/test_sphinx_config.py @@ -2,6 +2,7 @@ import pytest +from sphinx_exec_code.configuration.path_config import InvalidPathError from sphinx_exec_code.configuration.values import SphinxConfigFolder @@ -12,6 +13,6 @@ def test_path_errors(): a.check_folder_exists(Path('does_not_exist')) assert str(e.value) == 'Directory "does_not_exist" not found! (configured by config_key_name)' - with pytest.raises(ValueError) as e: + with pytest.raises(InvalidPathError) as e: a.make_path(None, 1) assert str(e.value) == 'Could not create Path from "1" (type int) (configured by config_key_name)' diff --git a/tox.ini b/tox.ini index f603fe0..b4c5442 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py38 py39 py310 + py311 flake docs @@ -12,8 +13,9 @@ envlist = python = 3.7: py37 3.8: py38 - 3.9: py39, flake, docs - 3.10: py310 + 3.9: py39 + 3.10: py310, flake, docs + 3.11: py311 [testenv] deps =