diff --git a/.gitignore b/.gitignore index fc6e29d054..8b01a4ad0e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ my_logger.csv /dist/ /docs/_build /docs/_theme -/docs/examples.rst +/docs/license.rst /docs/release_notes.rst /examples/vhdl/array_axis_vcs/src/test/data/out.csv /tests/acceptance/*_out diff --git a/docs/conf.py b/docs/conf.py index 490b1daef8..3a9f2ea3ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- -import os -import sys +from sys import path as sys_path +from os.path import abspath from pathlib import Path +sys_path.insert(0, abspath(".")) + # -- Sphinx Options ----------------------------------------------------------- # If your project needs a minimal Sphinx version, state it here. @@ -15,6 +17,7 @@ "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinxarg.ext", # Automatic argparse command line argument documentation + "exec", ] autodoc_default_options = { diff --git a/tools/docs_utils.py b/docs/examples.py similarity index 63% rename from tools/docs_utils.py rename to docs/examples.py index 1f2ddff9c9..e3d342c3ab 100644 --- a/tools/docs_utils.py +++ b/docs/examples.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. @@ -5,7 +7,7 @@ # Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com """ -Helper functions to generate examples.rst from docstrings in run.py files +Helper functions to generate content in examples.rst from docstrings in run.py files """ import sys @@ -15,37 +17,35 @@ from pathlib import Path from subprocess import check_call +from io import StringIO +from contextlib import redirect_stdout +from textwrap import indent + ROOT = Path(__file__).parent.parent / "docs" -def examples(): +def examples(debug=False): """ Traverses the examples directory and generates examples.rst with the docstrings """ eg_path = ROOT.parent / "examples" - egs_fptr = (ROOT / "examples.rst").open("w+") - egs_fptr.write( - "\n".join( - [ - ".. _examples:\n", - "Examples", - "========\n", - ".. include:: examples_intro.rst\n", - ] - ) - ) for language, subdir in {"VHDL": "vhdl", "SystemVerilog": "verilog"}.items(): - egs_fptr.write("\n".join([language, "~~~~~~~~~~~~~~~~~~~~~~~", "\n"])) + print("\n".join([language, "~~~~~~~~~~~~~~~~~~~~~~~", "\n"])) for item in listdir(str(eg_path / subdir)): loc = eg_path / subdir / item if loc.is_dir(): - _data = _get_eg_doc( - loc, - "https://github.com/VUnit/vunit/tree/master/examples/%s/%s" % (subdir, item), - ) + f = StringIO() + with redirect_stdout(f): + _data = _get_eg_doc( + loc, + "https://github.com/VUnit/vunit/tree/master/examples/%s/%s" % (subdir, item), + ) + if debug: + print(".. NOTE::") + print(indent(f.getvalue(), " * ")) if _data: - egs_fptr.write(_data) + print(_data) def _get_eg_doc(location: Path, ref): @@ -81,16 +81,5 @@ def _get_eg_doc(location: Path, ref): return "\n".join([title, "-" * len(title), eg_doc.split("---\n", 1)[1], "\n"]) -def get_theme(path: Path, url: str): - """ - Check if the theme is available locally, retrieve it with curl and tar otherwise - """ - tpath = path / "_theme" - if not tpath.is_dir() or not (tpath / "theme.conf").is_file(): - if not tpath.is_dir(): - tpath.mkdir() - zpath = path / "theme.tgz" - if not zpath.is_file(): - check_call(["curl", "-fsSL", url, "-o", str(zpath)]) - tar_cmd = ["tar", "--strip-components=1", "-C", str(tpath), "-xvzf", str(zpath)] - check_call(tar_cmd) +if __name__ == "__main__": + examples(True) diff --git a/docs/examples_intro.rst b/docs/examples.rst similarity index 85% rename from docs/examples_intro.rst rename to docs/examples.rst index 913aa5c656..59152dc264 100644 --- a/docs/examples_intro.rst +++ b/docs/examples.rst @@ -1,3 +1,8 @@ +.. _examples: + +Examples +======== + .. HINT:: Most of the examples expect the simulator to support several VHDL 2008 features. This is made explicit by using ``context`` instead of multiple ``use`` statements. @@ -5,3 +10,7 @@ handle contexts. In those cases, replacing ``context vunit_lib.vunit_context`` with the content of :vunit_file:`vunit/vhdl/vunit_context.vhd` and :vunit_file:`vunit/vhdl/data_types/src/data_types_context.vhd` might work. + +.. exec:: + from examples import examples + examples() diff --git a/docs/exec.py b/docs/exec.py new file mode 100644 index 0000000000..cbfbb43e0f --- /dev/null +++ b/docs/exec.py @@ -0,0 +1,45 @@ +# https://stackoverflow.com/questions/7250659/how-to-use-python-to-programmatically-generate-part-of-sphinx-documentation/18143318 + +import sys +from os.path import basename + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from docutils.parsers.rst import Directive +from docutils import nodes, statemachine + + +class ExecDirective(Directive): + """Execute the specified python code and insert the output into the document""" + + has_content = True + + def run(self): + oldStdout, sys.stdout = sys.stdout, StringIO() + + tab_width = self.options.get("tab-width", self.state.document.settings.tab_width) + source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1) + + try: + exec("\n".join(self.content)) + text = sys.stdout.getvalue() + lines = statemachine.string2lines(text, tab_width, convert_whitespace=True) + self.state_machine.insert_input(lines, source) + return [] + except Exception: + return [ + nodes.error( + None, + nodes.paragraph(text="Unable to execute python code at %s:%d:" % (basename(source), self.lineno)), + nodes.paragraph(text=str(sys.exc_info()[1])), + ) + ] + finally: + sys.stdout = oldStdout + + +def setup(app): + app.add_directive("exec", ExecDirective) diff --git a/tools/build_docs.py b/tools/build_docs.py index daa1437a10..6212225acd 100644 --- a/tools/build_docs.py +++ b/tools/build_docs.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. @@ -14,18 +16,31 @@ from sys import argv from shutil import copyfile from create_release_notes import create_release_notes -from docs_utils import examples, get_theme DROOT = Path(__file__).parent.parent / 'docs' +def get_theme(path: Path, url: str): + """ + Check if the theme is available locally, retrieve it with curl and tar otherwise + """ + tpath = path / "_theme" + if not tpath.is_dir() or not (tpath / "theme.conf").is_file(): + if not tpath.is_dir(): + tpath.mkdir() + zpath = path / "theme.tgz" + if not zpath.is_file(): + check_call(["curl", "-fsSL", url, "-o", str(zpath)]) + tar_cmd = ["tar", "--strip-components=1", "-C", str(tpath), "-xvzf", str(zpath)] + check_call(tar_cmd) + + def main(): """ Build documentation/website """ create_release_notes() - examples() copyfile(str(DROOT / '..' / 'LICENSE.rst'), str(DROOT / 'license.rst')) get_theme( DROOT,