diff --git a/doc/description.rst b/doc/description.rst index ead02d0..7fb413f 100644 --- a/doc/description.rst +++ b/doc/description.rst @@ -4,18 +4,22 @@ Usage Without options ------------------------------ -.. code-block:: rst +.. code-block:: python .. exec_code:: print('Easy!') -Generated view: +Generated view + +---- .. exec_code:: print('Easy!') +---- + Options ------------------------------ It's possible to further configure both the code block and the output block with the following options: @@ -34,7 +38,7 @@ language/language_output: Example: -.. code-block:: rst +.. code-block:: python .. exec_code:: :linenos: @@ -43,7 +47,9 @@ Example: print('Easy!') -Generated view: +Generated view + +---- .. exec_code:: :linenos: @@ -52,12 +58,15 @@ Generated view: print('Easy!') +---- + -Hide code parts +Code Markers ------------------------------ It's possible to hide parts of the code (e.g. to setup a working example) and it's possible to skip part of the code execution. This is possible with the ``#hide:[start|stop|toggle]`` or ``#skip:[start|stop|toggle]`` marker in the code. +Empty lines after a disabling marker will be ignored. Spaces and dashes are ignored for the case insensitive marker detection so these are all the same: @@ -69,33 +78,75 @@ Spaces and dashes are ignored for the case insensitive marker detection so these # ----- hide: start ----- -Example: - -.. code-block:: rst +Hiding code parts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: python .. exec_code:: # --- hide: start --- print('Setup!') #hide:toggle + print('Easy!') + # --- hide: start --- print('Hidden!') # --- hide: stop --- + # Note the missing entries! print('Visible!') -Generated view: +Generated view (note the skipped empty lines after the stop and disabling toggle marker) + +---- .. exec_code:: # --- hide: start --- print('Setup!') #hide:toggle + print('Easy!') + # --- hide: start --- print('Hidden!') # --- hide: stop --- + # Note the missing entries! print('Visible!') + +---- + +Skipping code parts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: python + + .. exec_code:: + + # --- skip: start --- + print(f'1 / 0 = {1 / 0}') + # --- skip: stop --- + + # --- hide: start --- + print('1 / 0 = 0') + # --- hide: stop --- + +Generated view + +---- + + .. exec_code:: + + # --- skip: start --- + print(f'1 / 0 = {1 / 0}') + # --- skip: stop --- + + # --- hide: start --- + print('1 / 0 = 0') + # --- hide: stop --- + +---- + +With the combination of ``skip`` and ``hide`` it's possible to "simulate" every code. diff --git a/readme.md b/readme.md index 1e6d21b..35fd071 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,9 @@ Sphinx-exec-code allows execution of any python code during the documentation bu It's also possible to display the output of the code execution. With this extension it's easy to ensure that the provided code samples are always working. -Additionally, it's possible to inspect and print output of the documented code. +Additionally, it's possible to show the output of the documented code. + +Each code snippet runs in a fresh interpreter so changes to not leak between executions. ## Documentation [The full documentation can be found at here](https://sphinx-exec-code.readthedocs.io) @@ -22,12 +24,20 @@ Additionally, it's possible to inspect and print output of the documented code. ````text .. exec-code:: - :hide_output: print('This code will be executed') ```` - +generates +```python +print('This code will be executed') +``` +``` +This code will be executed +``` # Changelog +#### 0.3 (24.09.2021) +- Added some more documentation and fixed some false path warnings + #### 0.2 (21.09.2021) - Initial Release diff --git a/src/sphinx_exec_code/__version__.py b/src/sphinx_exec_code/__version__.py index b650ceb..cce384d 100644 --- a/src/sphinx_exec_code/__version__.py +++ b/src/sphinx_exec_code/__version__.py @@ -1 +1 @@ -__version__ = '0.2' +__version__ = '0.3' diff --git a/src/sphinx_exec_code/code_format.py b/src/sphinx_exec_code/code_format.py index c6a57de..9175e0a 100644 --- a/src/sphinx_exec_code/code_format.py +++ b/src/sphinx_exec_code/code_format.py @@ -1,41 +1,73 @@ -from typing import Iterable, List, Tuple +from typing import Iterable, Tuple class VisibilityMarkerError(Exception): pass -def _process_code(code_lines: Iterable[str], marker: str) -> List[str]: +class CodeMarker: + MARKERS = ('hide', 'skip') - marker_start = f'#{marker}:start' - marker_stop = f'#{marker}:stop' - marker_toggle = f'#{marker}:toggle' + def __init__(self, marker: str): + assert marker in CodeMarker.MARKERS + self.start = f'#{marker}:start' + self.stop = f'#{marker}:stop' + self.toggle = f'#{marker}:toggle' + + self.do_add = True + self.skip_empty = False + self.lines = [] + + def is_marker(self, line: str) -> bool: + if line == self.start: + if not self.do_add: + raise VisibilityMarkerError(f'{self.start[1:]} already called! ' + f'Use {self.stop[1:]} or {self.toggle[1:]}!') + self.do_add = False + return True + + if line == self.stop: + if self.do_add: + raise VisibilityMarkerError(f'{self.stop[1:]} already called! ' + f'Use {self.start[1:]} or {self.toggle[1:]}!') + self.do_add = True + self.skip_empty = True + return True + + if line == self.toggle: + self.do_add = not self.do_add + return True + + return False + + def add_line(self, line: str): + if not self.do_add: + return None + + if self.skip_empty: + if not line.strip(): + return None + self.skip_empty = False + + self.lines.append(line) + + +def get_show_exec_code(code_lines: Iterable[str]) -> Tuple[str, str]: + hide = CodeMarker('hide') + skip = CodeMarker('skip') - lines = [] - do_add = True for org_line in code_lines: line = org_line.replace(' ', '').replace('-', '').lower() - if line == marker_start: - if not do_add: - raise VisibilityMarkerError(f'{marker}:start already called! Use {marker}:stop or {marker}:toggle!') - do_add = False - continue - elif line == marker_stop: - if do_add: - raise VisibilityMarkerError(f'{marker}:stop already called! Use {marker}:start or {marker}:toggle!') - do_add = True + + if hide.is_marker(line): continue - elif line == marker_toggle: - do_add = not do_add + if skip.is_marker(line): continue - if do_add: - lines.append(org_line) - return lines - + hide.add_line(org_line) + skip.add_line(org_line) -def get_show_exec_code(code_lines: Iterable[str]) -> Tuple[str, str]: - shown_code = '\n'.join(_process_code(code_lines, 'hide')) - executed_code = '\n'.join(_process_code(code_lines, 'skip')) + shown_code = '\n'.join(hide.lines) + executed_code = '\n'.join(skip.lines) return shown_code.strip(), executed_code.strip() diff --git a/src/sphinx_exec_code/sphinx_api.py b/src/sphinx_exec_code/sphinx_api.py index 439d8aa..49d9901 100644 --- a/src/sphinx_exec_code/sphinx_api.py +++ b/src/sphinx_exec_code/sphinx_api.py @@ -32,8 +32,9 @@ def builder_ready(app): raise FileNotFoundError(f'Additional directory "{_f}" not found! (configured by {CONF_NAME_DIRS})') # Search for a python package and print a warning if we find none - # since this is the only reason to specify a working dir + # since this is the only reason to specify additional folders for _f in folders: + package_found = False for __f in _f.iterdir(): if not __f.is_dir(): continue @@ -41,9 +42,13 @@ def builder_ready(app): # log warning if we don't find a python package for file in __f.iterdir(): if file.name == '__init__.py': + package_found = True break - else: - log.warning(f'[exec-code] No Python package found in {_f}') + if package_found: + break + + if not package_found: + log.warning(f'[exec-code] No Python packages found in {_f}') setup_code_env(cwd, folders) return None diff --git a/src/sphinx_exec_code/sphinx_exec.py b/src/sphinx_exec_code/sphinx_exec.py index a12d3fe..96ff46b 100644 --- a/src/sphinx_exec_code/sphinx_exec.py +++ b/src/sphinx_exec_code/sphinx_exec.py @@ -57,14 +57,17 @@ def _run(self) -> list: :return: """ output = [] + file, line = self.get_source_info() # format the code - code_show, code_exec = get_show_exec_code(self.content) + try: + code_show, code_exec = get_show_exec_code(self.content) + except Exception as e: + raise ExtensionError(f'Could not parse code markers at {self.get_location()}', orig_exc=e) # Show the code from the user create_literal_block(output, code_show, spec=SpecCode.from_options(self.options)) - file, line = self.get_source_info() try: code_results = execute_code(code_exec, file, line) except CodeException as e: diff --git a/tests/test_code_format.py b/tests/test_code_format.py index 30f88a6..dc4ff4b 100644 --- a/tests/test_code_format.py +++ b/tests/test_code_format.py @@ -4,9 +4,17 @@ def test_format_hide(): - code = 'print("1")\n# - hide: start - \nprint("2")\n #hide:stop\nprint("3")' + 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")' + + +def test_format_skip(): + code = 'print("1")\n# - skip: start - \nprint("2")\n #skip:stop\nprint("3")' + show, run = get_show_exec_code(code.splitlines()) + assert show == 'print("1")\nprint("2")\nprint("3")' + assert run == 'print("1")\nprint("3")' def test_marker_err():